/*
INSTCMD.C 

The "Installable Command" function is _not_ called by a program that
wants to extend COMMAND.COM's repertoire. Instead, you hook the
function and wait for COMMAND.COM to call you ("don't call us, we'll
call you"). Function AE00h lets you tell COMMAND.COM whether you
want to handle the command, and function AE01h is where you actually
handle it (similar to device driver division of labor between
Strategy and Interrupt).

Note that AE01h is called with only the name of the command: not with
any arguments. Therefore, arguments must be saved away in AE00h. Yuk!

Furthermore, while redirection _is_ handled in the normal way in
AE01, in AE00 we get the entire command string, including any
redirection or piping. Therefore, these must be stripped off before
saving away the args during AE00 processing.

Problem with following 2FAE00 and 2FAE01 handlers: they should
chain to previous handler. For example, with existing code, INTRSPY program
won't see 2FAE00 and 2FAE01 calls once INSTCMD is installed. 

The sample COMMAND.COM extension used here is FULLNAME, based on the
undocumented TRUENAME command in DOS 4.x. We simply run undocumented
Function 60h in order to provide FULLNAME. Actually, not _quite_ so
simple, since Function 60h doesn't like leading or trailing spaces.
These are handled inside function fullname().

The following INTRSPY script was helpful in debugging 2FAE:

; INSTCMD.SCR
structure cmdline fields
    max (byte)
    text (byte,string,64)

intercept 2fh
    function 0aeh
        subfunction 00h
            on_entry
                if (dx == 0FFFFh)
                    output "AE00" 
                    output (DS:BX->cmdline)
                    output CH		; FFh first time, 0 second time
                    output ""
        subfunction 01h
            on_entry
                if (dx == 0FFFFh)
                    output "AE01"
                    output (DS:SI->byte,string,64)
                        
requires Microsoft C 6.0+, or Quick C 2.0+
    cl -qc instcmd.c
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <conio.h>
#include <dos.h>

#pragma pack(1)

typedef struct {
    unsigned es,ds,di,si,bp,sp,bx,dx,cx,ax;
    unsigned ip,cs,flags;
    } REG_PARAMS;
    
typedef unsigned char BYTE;

typedef struct {
    BYTE len;
    BYTE txt[1];
    } STRING;

typedef struct {
    BYTE max;
    STRING s;
    } CMDLINE;

void interrupt far handler_2f(REG_PARAMS r);

void (interrupt far *old)();

void fail(char *s) { puts(s); exit(1); }

main(void)
{
    /* hook INT 2F */
    old = _dos_getvect(0x2f);
    _dos_setvect(0x2f, handler_2f);
    
    puts("This demo of installable commands isn't a TSR.");
    puts("Instead, it just creates a subshell from which you can EXIT");
    puts("when done. In the subshell, one new command has been added:");
    puts("FULLNAME [filename].");
    
    system(getenv("COMSPEC"));
    
    /* unhook INT 2F */
    _dos_setvect(0x2f, old);
}

char far *fullname(char far *s, char far *d)
{
    char far *s2;
    
    /* INT 21h AH=60h doesn't like leading or trailing blanks */
    while (isspace(*s))
        s++;
    s2 = s;
    while (*s2) s2++;
    s2--;
    while (isspace(*s2))
        *s2-- = 0;

    _asm {
        push di
        push si
        les di, d
        lds si, s
        mov ah, 60h
        int 21h
        pop si
        pop di
        jc error
        }

    return d;
error:
    return (char far *) 0;
}

void fcputs(char far *s)
{
    /* can't use stdio (e.g., putchar) inside 2FAE01 handler? */
    while (*s) 
        putch(*s++); 
    putch(0x0d); putch(0x0a);
}

char buf[128];      /* not reentrant */
char args[128];

#define CMD_LEN     8

void interrupt far handler_2f(REG_PARAMS r)
{
    if ((r.ax == 0xAE00) && (r.dx == 0xFFFF))
    {
        CMDLINE far *cmdline;
        int len;
        FP_SEG(cmdline) = r.ds;
        FP_OFF(cmdline) = r.bx;
        len = min(CMD_LEN, cmdline->s.len);
        if ((_fmemicmp(cmdline->s.txt, "fullname", len) == 0) ||
            (_fmemicmp(cmdline->s.txt, "FULLNAME", len) == 0))
        {
            char far *redir;
            int argslen = cmdline->s.len - CMD_LEN;
            _fmemcpy(args, cmdline->s.txt + CMD_LEN, argslen);
            args[argslen] = 0;
            /* yuk! we have to get rid of redirection ourselves! */
            /* it will still take effect in AE01 */
            /* the following is not really correct, but okay for now */
            if (redir = _fstrrchr(args, '>'))
                *redir = 0;
            if (redir = _fstrrchr(args, '<'))
                *redir = 0;
            if (redir = _fstrrchr(args, '|'))
                *redir = 0;
            r.ax = 0xAEFF;      /* we will handle this one */
        }
    }
    else if ((r.ax == 0xAE01) && (r.dx == 0xFFFF))
    {
        STRING far *s;
        int len;
        FP_SEG(s) = r.ds;
        FP_OFF(s) = r.si;
        len = min(CMD_LEN, s->len);
        if ((_fmemicmp(s->txt, "fullname", len) == 0) ||
            (_fmemicmp(s->txt, "FULLNAME", len) == 0))
        {
            char far *d;
            if (! *args)
                d = "syntax: fullname [filename]";
            else if ((d = fullname(args, buf)) == 0)
                d = "invalid filename";
            fcputs(d);
            s->len = 0; /* we handled it; COMMAND.COM shouldn't */
        }
    }
    else
        _chain_intr(old);
}
