/*
  XSEND - Generates command files for MS-DOS Kermit, ftp, and other PC file
  transfer programs, to send and replicate tree-structured file systems.

    Mon,  4 Mar 91 - Frank da Cruz, Columbia University
    Minor corrections to syntax of constructed Kermit commands.

    Fri, 21 Apr 89 - Vace Kundakci, Columbia University
    Version 0.5V add tftp (and other) support and parse switches on the
        arg line

    Thu, 20 Apr 89 - Vace Kundakci, Columbia University
    Version 0.4V eliminate full path specification to server and add cd ..

    Program created by, and all previous edits by, Mark Zinzow:

    Wednesday, March 30, 1988
    Version 0.4 added blank lines for server cwd password

    Saturday, November 21, 1987
    Version 0.3 added kludged turbo dir functions for IBM & Microsoft C

    Version 0.2 attempt to do above with far pointers to ffblk failed.
    Version 0.1
        by Mark S. Zinzow
        (MARKZ@UIUCVMD.BITNET or markz@vmd.cso.uiud.edu)

    Program to generate TAKE (script) files for MS-DOS Kermit to allow it
    to send files and directories to a server over entire tree branches or
    disks.

    Change directory to the desired directory in which you wish all
    files and subdirectories copied.  Then run XSEND redirecting the
    output with the dos redirection symbol ">". (e.g. C:>XSEND >takeme)
    Then establish a connection to the remote system with kermit and put
    the remote system in server mode.  Then just take the take file on the
    local system.  If you wish to avoid the password prompt on the remote
    cwd commands use MS-Kermit 2.30/A or later and redirect the takeme
    file into it.  (For example KERMIT <takeme but don't forget to make
    sure you have the right parity, baud, and port etc. set in your ini
    file, or use a text editor to add the appropriate set commands to your
    take file.)

    Compileable with Microsoft C 5.0 and Borland TurboC Version 1.0
*/

#include <stdio.h>

#ifdef __TURBOC__
#include <dir.h>
#endif

#include <dos.h>
#include <string.h>

#ifndef __TURBOC__

/* from Turbo errno.h  */
#define ENOENT  2       /* No such file or directory */
#define ENMFILE 18      /* No more files */

extern int errno;

/* From turbo dir.h */
struct  ffblk {
    char        ff_reserved[21];
    char        ff_attrib;
    unsigned    ff_ftime;
    unsigned    ff_fdate;
    long        ff_fsize;
    char        ff_name[13];
};

/* From Turbo dos.h */
#define FA_RDONLY   0x01    /* Read only attribute */
#define FA_HIDDEN   0x02    /* Hidden file */
#define FA_SYSTEM   0x04    /* System file */
#define FA_LABEL    0x08    /* Volume label */
#define FA_DIREC    0x10    /* Directory */
#define FA_ARCH     0x20    /* Archive */

#endif

#define FILEALL     0xF7
#define FILENONE    0x16

#define MAXDIRLEN   84  /* maximum string length for path strings */
                /* the dos maximum is 64 bytes */
#ifdef __TURBOC__
int ls (char *twd);
#endif

char *comm;
char *move;
char *md;
char *cd;
char opc;
char hn[64];
char fn[13];
int vflag;
int cwdlen;

main (argc,argv) int argc; char *argv[]; {
    int i;
    char cwd[MAXDIRLEN]; /* string to hold Current Working Directory */
    char pn[MAXDIRLEN];
    char c[MAXDIRLEN];

    opc = 'k';
    strcpy(hn,"");
    strcpy(fn,"*.*");
    strcpy(pn,"");
    md = "mkdir";
    cd = "cd";
    comm = "rem";
    vflag = 0;

    for (i = 1; i < argc; i++) {
        strcpy(c,argv[i]);
        if (c[0] == '-') {
            switch(c[1]) {
                case 'g':
                case 'p':
                case 'm':
                case 'z':
                case 'l':
                case 'c':
                case 'k': opc = c[1]; strcpy(pn,&c[2]); break;

                case 'h': strcpy(hn,&c[2]); break;

                case 'v': vflag = 1; break;

                case 'f': strcpy(fn,&c[2]); break;

                default:
                    printf("xsend switches: ckmgpzlvf\n");
                    exit(0);
            }
        } else {
            printf("usage: xsend switches\n");
            printf("  -k[path]: kermit commands (default)\n");
            printf("  -c[path]: copy commands\n");
            printf("  -m[path]: mkdir commands\n");
            printf("  -z[path]: del and rmdir commands\n");
            printf("  -l[path]: list full names\n");
            printf("  -g[path]: tftp get commands\n");
            printf("  -p[path]: tftp put commands\n");
            printf("  -h<name>: tftp to/from host\n");
            printf("  -f<name>: select files with name\n");
            printf("  -v: verbose (applies for -cklz)\n");
            exit(0);
        }
    }

    switch(opc) {
        case 'm': vflag = 0; break;
        case 'z': move = "del"; break;
        case 'c': move = "copy"; break;
        case 'l': move = "f"; break;

        case 'g':
        case 'p':
            if (hn[0] == '\0') {
                printf("no hostname");
                exit(0);
            }
            move = "tftp"; vflag = 1; break;

        case 'k':
            md = "remote host mkdir"; cd = "remote cd";
            move = "send"; comm = "echo"; break;
    }

    printf("%s XSEND Version 0.5V\n",comm);

    if (pn[0] == '\0') getcwd(cwd,MAXDIRLEN); else strcpy(cwd,pn);
    i = strlen(cwd) - 1;
    if (i >= 0 && cwd[i] == '\\') cwd[i] = '\0'; /* chop ending \ */
    cwdlen = strlen(cwd);
    ls(cwd);
}

ls(twd) char twd[]; {
    char xwd[MAXDIRLEN];
    char far *p;
    struct ffblk ffblk;
    int done;

    strcpy(xwd,twd);
    strcat(xwd,"\\");
    strcat(xwd,fn);
    p = &xwd[0];
    if (!vflag && (opc != 'm') && (opc != 'l'))
        printf("%s %s\n",move,xwd);

    done = findfirst(p,&ffblk,FILEALL) || !vflag;
    while (!done) {
        if (!(ffblk.ff_attrib & FILENONE)) {
        switch (opc) {
            case 'p':
            if (cwdlen == strlen(twd))
                printf("tftp -p %s\\%s %s %s image\n",
                twd,ffblk.ff_name,hn,ffblk.ff_name);
            else
                printf("tftp -p %s\\%s %s %s\\%s image\n",
                twd,ffblk.ff_name,hn,&twd[cwdlen + 1],
                ffblk.ff_name);
            break;

            case 'g':
            printf("tftp -g %s\\%s %s %s image\n",
                twd,ffblk.ff_name,hn,ffblk.ff_name);
            break;

            case 'z':
            case 'c':
            case 'l':
            case 'k':
            printf("%s %s\\%s\n",move,twd,ffblk.ff_name);
        }
        }
        done = findnext(&ffblk);
    }

    strcpy(xwd,twd);
    strcat(xwd,"\\*.*");
    p = &xwd[0];

    done = findfirst(p,&ffblk,FILEALL); /* &ffblk seems to be near */
    while (!done) {
        if ((ffblk.ff_attrib & FA_DIREC) && (ffblk.ff_name[0] != '.' )) {
        strcpy(xwd,twd);
        strcat(xwd,"\\");
        strcat(xwd,ffblk.ff_name);
        if ((opc != 'l') && (opc != 'p') && (opc != 'z'))
            printf("%s %s\n",md,ffblk.ff_name);
        if (opc != 'l') printf("%s %s\n\n",cd,ffblk.ff_name);
        else if (!vflag) printf ("d %s\n",xwd);
        ls(xwd);
        if (opc != 'l') printf("%s ..\n",cd);
        if (opc == 'z') printf("rmdir %s\n",ffblk.ff_name);
        }
        done = findnext(&ffblk);
    }
    return;
}

# ifndef __TURBOC__

/*
    function written from description in Turbo reference and dos manual
    without turbo source for compatibility with other compiler(s?).  I
    added far to the declarations as I can't figure out how to do this
    near and far as a general case.  Perhaps some clever use of sizeof?
*/

findfirst(pathname, ffblk, attrib)
char far *pathname;
struct ffblk *ffblk;
int attrib;
{
    struct SREGS sregs;
    union REGS inregs, outregs;
    int result;
    char far *p;
    extern int errno;

/* Set Disk Transfer Area to ffblk structure passed */

    segread(&sregs);        /* Set DTA wants DS:DX to point to */
    inregs.x.dx = FP_OFF(ffblk);    /* the disk transfer area to be set */
    inregs.h.ah = 0x1a;     /* Set Disk Transfer Area */
    result = int86x(0x21,&inregs,&outregs,&sregs);

    sregs.ds = FP_SEG(pathname);    /* FindFirst wants DS:DX to point */
    inregs.x.dx = FP_OFF(pathname); /* to an ASCIIZ string */
    inregs.x.cx = attrib;
    inregs.h.ah = 0x4e;     /* FindFirst */
    errno = int86x(0x21,&inregs,&outregs,&sregs);

    if (errno == ENOENT || errno == ENMFILE) return(-1);
    return(0);
}

int findnext(ffblk) struct ffblk far *ffblk; {
    struct SREGS sregs;
    union REGS inregs, outregs;
    int result;
    extern int errno;

/*
    I use the current DS register as I can't figure out how to get a far
    address to a structure declared and passed, so I'm assuming near.  My
    appologies for this brute force and ignorance (trial and error)
    solution.  Please send me the more elegant method if you can get the
    compiler to do it!  When declaring far *ffblk FP_SEG returned attrib!
    - mz
*/

/* Set Disk Transfer Area to ffblk structure passed */

    segread(&sregs);
    inregs.x.dx = FP_OFF(ffblk);    /* the disk transfer area to be set */
    inregs.h.ah = 0x1a;     /* Set Disk Transfer Area */
    result = int86x(0x21,&inregs,&outregs,&sregs);

    inregs.h.ah = 0x4f;     /* FindNext */
    errno = int86x(0x21,&inregs,&outregs,&sregs);

    if (errno == ENMFILE) return(-1);
    return(0);
}
#endif
