/*
 *				f l x . c
 *
 * Read and write Dec "Dos" magtapes on Unix.  The tape is mounted
 * on device 0.
 *
 * Usage
 *	flx option files
 *
 * Options
 *
 *	a	Ascii	-- extract removes '\r' and '\0' from the file.
 *	c	Create	-- write files to the tape.
 *	t	Table	-- print a tape directory.
 *	v	Verbose	-- log files as seen.
 *	u [ppn]	Uic	-- the next argument is a project-programmer
 *				number.  The values are in octal unless
 *				they are followed by '.':
 *					[100,2]	== [64.,2.]
 *	*	Wild	-- Extract all files from all Uic's
 */

#include <stdio.h>
long tbuf;
#include <time.h>
#define NBLOCK  512
#define MINREC  14

struct  label
{
        int     l_name0[2];             /* Two radix 50 name words */
        int     l_type[1];              /* Radix 50 type */
        char    l_prog;                 /* Programmer number */
        char    l_proj;                 /* Project number */
        int     l_prot;                 /* Protection code */
        int     l_date;                 /* Date (in some format) */
        int     l_name1[1];             /* One radix 50 name word */
};

int     func;
int     aflag;
int     vflag;
int	starflag;
int     estat;
char    bbuf[NBLOCK];
char    *bbufpt;
int     mtfd;

char    rmt[]   = "/dev/rmt0";		/* Device to read from		*/
char    rmtn[]  = "/dev/rmtn";		/* This needs editing		*/
int     proj    = 1;
int     prog    = 1;

main(argc, argv)
char *argv[];
{
        register char *p;
        register c, i;

        if (argc < 2)
                usage();
        i = 2;
        p = argv[1];
        while ((c = *p++) != 0) {
                switch (c) {

                case 'c':			/* Create magtape	*/
                case 't':			/* Table (directory)	*/
                case 'x':			/* Extract from tape	*/
                        if (func != 0)
                                usage();
                        func = c;
                        break;

                case 'u':			/* Set ppn		*/
                        if (i >= argc)
                                usage();
                        projprog(argv[i++]);
                        break;

                case 'v':			/* Verbose		*/
                        ++vflag;
                        break;

                case 'a':			/* Ascii -- drop <CR>	*/
                        ++aflag;
                        break;

		case '*':			/* Force *.*		*/
			++starflag;
			break;

                default:
                        usage();
                }
        }
        switch (func) {

        case 'c':
                create(i, argc, argv);
                break;

        case 't':
                table();
                break;

        case 'x':
                extract(i, argc, argv);
                break;

        default:
                usage();
        }
        exit (estat);
}

/*
 * The argument `cp' is a pointer
 * to a string of the form `[nnn,nnn]';
 * i.e. a project-programmer number.
 * Reset the external variables `proj'
 * and `prog' to agree with the arg.
 * Quit if the format is bad.
 */
projprog(cp)
register char *cp;
{
        register c;
	register int ippn;
	int	decimal;

        if (*cp++ != '[')
                badppn();
        proj = 0;
	decimal = 0;
	ippn = atoi(cp);
        while ((c = *cp++)>='0' && c<='9') {
		if (c == '8' || c == '9')
			decimal = 1;
                proj = 8*proj + c - '0';
	}
	if (c == '.') {
		c = *cp++;
		decimal = 1;
	}
	if (decimal)
		proj = ippn;
        if (c != ',')
                badppn();
        prog = 0;
	decimal = 0; ippn = atoi(cp);
        while ((c = *cp++)>='0' && c<='9') {
		if (c == '8' || c == '9')
			decimal = 1;
                prog = 8*prog + c - '0';
	}
	if (c == '.') {
		c = *cp++;
		decimal = 1;
	}
	if (decimal)
		prog = ippn;
        if (c != ']')
                badppn();
	if (vflag) {
		fprintf(stderr, "[%03o,%03o] == (%d.,%d.)\n",
			proj, prog, proj, prog);
	}
}
/*
 * Print out a diagnostic for
 * bad project-programmer numbers and
 * quit.
 */
badppn()
{
        diag(1, "bad [project,programmer] specification");
}

/*
 * Process the table command.
 * Read through the FLX tape, printing out
 * the information in the headers. The data
 * blocks are counted as they are read, just
 * in case this is `tv'. Quit on the double
 * tapemark at the end.
 */
table()
{
        register fd, n, nblocks;
        struct  label lb;

        if ((fd=open(rmt, 0)) < 0)
                diag(1, "cannot open tape");
        for (;;) {
                if ((n=readlabel(fd, (char *)&lb, sizeof(lb))) <= 0)
                        break;
                nblocks = 0;
                while (read(fd, bbuf, sizeof(bbuf)) > 0)
                        ++nblocks;
                n = printname(&lb);
                if (vflag) {
                        while (n < 14) {
                                putchar(' ');
                                ++n;
                        }
                        printf("%5d", nblocks);
                        printf(" [%03o,%03o]", lb.l_proj&0377, lb.l_prog&0377);
                        printdate(lb.l_date);
                        printf(" <%03o>", lb.l_prot&0377);
                }
                putchar('\n');
        }
        close(fd);
        if (n != 0)
                diag(1, "tape read error");
}

/*
 * Given a pointer to a structure
 * containing the FLX file label record,
 * convert the name from RADIX 50 to
 * Ascii and write it to the standard output.
 * The number of characters written out is
 * returned, just in case this is `tv'.
 */
printname(lbp)
struct  label   *lbp;
{
        register char   *p;
        register        c, n;
        char            b[9];

        r50toa(&b[0], lbp->l_name0, 2);
        r50toa(&b[6], lbp->l_name1, 1);
        n = 0;
        p = &b[0];
        while (p<&b[9] && (c = *p++)!=' ') {
                putchar(c);
                ++n;
        }
        if (lbp->l_type[0] != 0) {
                putchar('.');
                ++n;
                r50toa(&b[0], lbp->l_type, 1);
                p = &b[0];
                while (p<&b[3] && (c = *p++)!=' ') {
                        putchar(c);
                        ++n;
                }
        }
        return (n);
}

/*
 * Convert `nr50' words worth of
 * RADIX 50 data, pointed to by the argument
 * `r50p', into `3*nr50' bytes of Ascii and
 * store the characters into the buffer
 * pointed to by the `cp' argument. The output
 * string is in lower case. The illegal code
 * in RADIX 50 is converted to a `?'.
 */
static char     ctable[] = {
	" abcdefghijklmnopqrstuvwxyz$.?0123456789"
};

r50toa(cp, r50p, nr50)
register char   *cp;
int      *r50p;
{
        register unsigned r50;
        register acx;

        while (nr50--) {
                r50 = *r50p++;
                acx = 0;
                while (r50 >= 03100) {
                        r50 -= 03100;
                        ++acx;
                }
                *cp++ = ctable[acx];
                acx = 0;
                while (r50 >= 050) {
                        r50 -= 050;
                        ++acx;
                }
                *cp++ = ctable[acx];
                *cp++ = ctable[r50];
        }
}

/*
 * Take a date word, convert the
 * date to a human being format and print
 * it on the standard output. The date word
 * is encoded as `1000*(year-1970)+daynum',
 * where January 1 is day number 1.
 */
static  char monlen[]   = {
	31,     0,      31,     30,     31,     30,
	31,     31,     30,     31,     30,     31
};
static  char *monname[] = {
	"Jan",  "Feb",  "Mar",  "Apr",  "May",  "Jun",
	"Jul",  "Aug",  "Sep",  "Oct",  "Nov",  "Dec"
};

printdate(date)
{
        register month, day, year;
        year = (date/1000) + 1970;
        day  =  date%1000;
        monlen[1] = ((year%4)==0 && (year%100)!=0) ? 29 : 28;
        month = 0;
        while (day > 31)
                day -= monlen[month++];
        printf(" %02d-%s-%02d", day, monname[month], year-1900);
}

extract(i, argc, argv)
char    *argv[];
{
        register char   *p;
        register        c, n;
        register        fd;
        struct  label   lb;
        register FILE   *ofp;
        char            ofn[40];

        if ((fd=open(rmt, 0)) < 0)
                diag(1, "cannot open tape drive");
        while ((n = readlabel(fd, &lb, sizeof (lb))) > 0) {
                ofp = NULL;
                makename(ofn, &lb);
                if (starflag || named(ofn, i, argc, argv)) {
                        if (vflag)
                                printf("Extract: %s\n", ofn);
                        stripuic(ofn);
                        if ((ofp=fopen(ofn, "w")) == NULL)
                                diag(0, "%s: cannot create output", ofn);
                } else if (vflag)
                        printf("Skipped: %s\n", ofn);
                while ((n=read(fd, bbuf, sizeof(bbuf))) > 0) {
                        if (ofp != NULL) {
                                p = &bbuf[0];
                                while (n--) {
                                        c = (*p++) & 0377;
                                        if (aflag == 0 || (c != 0 && c != 015))
                                                putc(c, ofp);
                                }
                        }
                }
                if (ofp != NULL)
                        fclose(ofp);
                if (n < 0)
			printf("Read error, code %d\n", n);
		/* if (n != 0)
                        break;
		*/
        }
        close(fd);
        if (n != 0)
                diag(1, "tape error");
}

int
readlabel(fd, buffer, buffersize)
FILE		*fd;
char		*buffer;
int		buffersize;
/*
 * Read a tape label.  Return 0 at eof (standard Unix) or -1 (Venix)
 */
{
	register int n;

	n = read(fd, buffer, buffersize);
	if (n <= 0) {
		printf("readlabel, at eof\n", n);
	}
	return (n);
}

named(ofn, i, argc, argv)
char    *ofn;
char    *argv[];
{
        if (i == argc)
                return (1);
        while (i < argc) {
                if (strcmp(ofn, argv[i]) == 0)
                        return (1);
                ++i;
        }
        return (0);
}

makename(cp, lp)
register char   *cp;
struct label    *lp;
{
        register char   *bp;
        register        c;
        char            b[9];

        sprintf(cp, "[%03o,%03o]", lp->l_proj&0377, lp->l_prog&0377);
        while (*cp != 0)
                ++cp;
        r50toa(&b[0], lp->l_name0, 2);
        r50toa(&b[6], lp->l_name1, 1);
        bp = &b[0];
        while (bp<&b[9] && (c = *bp++)!=' ')
                *cp++ = c;
        if (lp->l_type[0] != 0) {
                *cp++ = '.';
                r50toa(&b[0], lp->l_type, 1);
                bp = &b[0];
                while (bp<&b[3] && (c = *bp++)!=' ')
                        *cp++ = c;
        }
        *cp = 0;
}

stripuic(cp1)
register char   *cp1;
{
        register char   *cp2;

        cp2 = cp1;
        while (*cp2 != 0)
                ++cp2;
        while (cp2!=cp1 && cp2[-1]!=']')
                --cp2;
        if (cp2 != cp1) {
                while (*cp1++ = *cp2++)
                        ;
        }
}

create(i, argc, argv)
char    *argv[];
{
        register FILE   *ifp;
        register        c;
        register        nbytes;
        struct label    lb;

        if (i == argc)
                diag(1, "no files");
        while (i < argc) {
                if ((ifp=fopen(argv[i], "r")) == NULL)
                        diag(0, "%s: cannot open", argv[i]);
                else {
                        if (vflag)
                                printf("Creating: %s\n", argv[i]);
                        if ((mtfd=open(rmtn, 1)) < 0)
                                diag(1, "cannot open tape drive");
                        makelabel(&lb, argv[i]);
                        if (write(mtfd, (char *)&lb, sizeof(lb)) != sizeof(lb))
                                diag(1, "tape write error");
                        nbytes = 0;
                        bbufpt = &bbuf[0];
                        while ((c=getc(ifp)) != EOF) {
                                if (aflag!=0 && c=='\n') {
                                        byte('\r');
                                        ++nbytes;
                                }
                                byte(c);
                                ++nbytes;
                        }
                        if ((nbytes&01) != 0)
                                byte(0);
                        flushtape();
                        close(mtfd);
                        fclose(ifp);
                }
                ++i;
        }
}

byte(b)
register b;
{
        if (bbufpt >= &bbuf[NBLOCK]) {
                flushtape();
                bbufpt = &bbuf[0];
        }
        *bbufpt++ = b;
}

flushtape()
{
        register n;

        if ((n=bbufpt-&bbuf[0]) != 0) {
                /*
                 * No record can be shorter than
                 * MINREC (14) bytes. So says ANSI and
                 * so checks the TJE16 tape drive.
                 */
                while (n < MINREC) {
                        *bbufpt++ = 0;
                        ++n;
                }
                if (write(mtfd, bbuf, n) != n)
                        diag(1, "tape write error");
        }
}

makelabel(lp, ifn)
struct  label   *lp;
char            *ifn;
{
        register char   *cp2, *cp3;
        register        c;
        struct tm       *tmp;
        struct tm       *localtime();
        char            b[12];

        cp2 = ifn;
        while (cp2[0] != 0)
                ++cp2;
        while (cp2!=ifn && cp2[-1]!='/')
                --cp2;
        cp3 = &b[0];
        while ((c = *cp2++)!=0 && c!='.') {
                if (cp3 < &b[9])
                        *cp3++ = c;
        }
        while (cp3 < &b[9])
                *cp3++ = ' ';
        if (c == '.') {
                while ((c = *cp2++) != 0) {
                        if (cp3 < &b[12])
                                *cp3++ = c;
                }
        }
        while (cp3 < &b[12])
                *cp3++ = ' ';
        ator50(lp->l_name0, 2, &b[0]);
        ator50(lp->l_name1, 1, &b[6]);
        ator50(lp->l_type,  1, &b[9]);
        time(&tbuf);
        tmp = localtime(&tbuf);
        lp->l_date = 1000*(tmp->tm_year-70) + tmp->tm_yday + 1;
        lp->l_proj = proj;
        lp->l_prog = prog;
        lp->l_prot = 0233;
}

ator50(r50p, nr50, cp)
register int    *r50p;
register char   *cp;
{
        register        r50;

        while (nr50--) {
                r50  = 050*050*r50char(*cp++);
                r50 += 050*r50char(*cp++);
                r50 += r50char(*cp++);
                *r50p++ = r50;
        }
}

r50char(c)
register c;
{
        if (c == ' ')
                return (0);
        if (c>='A' && c<='Z')
                return (c - 'A' + 01);
        if (c>='a' && c<='z')
                return (c - 'a' + 01);
        if (c == '$')
                return (033);
        if (c == '.')
                return (034);
        if (c>='0' && c<='9')
                return (c - '0' + 036);
        return (035);
}

diag(flag, a)
{
        fprintf(stderr, "flx: %r\n", &a);
        if (flag != 0)
                exit(1);
        estat = 1;
}

usage()
{
        fprintf(stderr, "Usage: flx [actvx*] [u uic] [files ...]\n");
	fprintf(stderr,	" a\tAscii extract -- ignore <CR> and <NULL>\n");
	fprintf(stderr, " c\tCreate -- write files to magtape\n");
	fprintf(stderr, " t\tTable -- print tape directory\n");
	fprintf(stderr, " v\tVerbose -- log files as seen\n");
	fprintf(stderr, " *\tCopy all files (*.*) from all accounts\n");
	fprintf(stderr, " u\tUic.  Next arg is [proj,prog]\n");
	fprintf(stderr, "\t\tThe '[', ',', and ']' are mandatory.\n");
	fprintf(stderr, "\t\tDecimal uic's are allowed, use '.'\n");  
        exit(1);
}
