/* Tap: read contents of a tap archive.
 * Author: Warren Toomey wkt@cs.adfa.edu.au
 * $Revision: 1.2 $
 * 
 * This works on a little-endian machine. You will probably have endian
 * trouble with the 2-, 3- and 4-octet fields in the tapdir structure.
 */

#include "defines.h"
#define BLKSIZ 512				/* Size of tap tape blocks */

/* Values for v1stat iflags */
#define V1_ST_SETUID	0000040
#define V1_ST_EXEC	0000020
#define V1_ST_OWNREAD	0000010
#define V1_ST_OWNWRITE	0000004
#define V1_ST_WRLDREAD	0000002
#define V1_ST_WRLDWRITE 0000001

/* tap tape directory entry */
struct tapdir {
    char pathname[32];				/* Filename of file */
    uint8_t mode;				/* 1st Edition mode */
    uint8_t uid;				/* Owner of file */
    uint16_t size;				/* Size in bytes */
    uint32_t modtime;				/* Time of last modification */
    uint16_t tapeaddr;				/* Beginning block on tape */
    char unused2[20];
    uint16_t checksum;				/* Checksum */
};

/* We build a linked list when
 * reading in the tape
 */
struct tlist {
    struct tapdir tdir;
    int size;
    struct tlist *next;
};

void mkrecursdir(char *name);

void swap16(i)
   uint16_t *i;
{
   char *a, b;

   a= (char *)i; b= a[0]; a[0]= a[1]; a[1]=b;
}

void swap32(i)
   uint32_t *i;
{
   char *a, b;

   a= (char *)i; b= a[0]; a[0]= a[3]; a[3]=b; b=a[1]; a[1]=a[2]; a[2]=b;
}

void checktypes()
{
 if (sizeof(int8_t)!=1)	  { printf("Wrong size for type int8_t\n"); exit(1); }
 if (sizeof(uint8_t)!=1)  { printf("Wrong size for type uint8_t\n"); exit(1); }
 if (sizeof(int16_t)!=2)  { printf("Wrong size for type int16_t\n"); exit(1); }
 if (sizeof(uint16_t)!=2) { printf("Wrong size for type uint16_t\n"); exit(1); }
 if (sizeof(int32_t)!=4)  { printf("Wrong size for type int32_t\n"); exit(1); }
 if (sizeof(uint32_t)!=4) { printf("Wrong size for type uint32_t\n"); exit(1); }
}

void usage()
{
    printf("Usage: tap epoch [t|x] filename\n");
    printf("   Epoch is the integers 71, 72 or 73\n");
    exit(1);
}

int main(argc, argv)
    int argc;
    char *argv[];
{
    char buf[BLKSIZ];
    FILE *zin, *zout;
    int toc = 1;
    int extract = 0;
    int mode;
    int epoch;				/* 71, 72 or 73 */
    uint32_t modtime;			/* Real mod time */
    char modstr[8];
    char *Ctime, *pathname;
    uint32_t size;
    struct tlist *thead = NULL, *tent, *this;
    int blockbytes;
    uint8_t a, *b;
    struct utimbuf utbuf;

    checktypes();			/* Check size of our typedefs */
    if (argc != 4) usage();		/* Give usage if wrong # args */

    epoch=atoi(argv[1]);		/* Get archive's epoch */
    switch(epoch) {
      case 71: case 72: case 73: break;
      default: usage();
    }
    epoch -= 70;
    if (argv[2][0] == 't') {
	toc = 1; extract = 0;
    }					/* Either extract of give contents */
    if (argv[2][0] == 'x') {
	toc = 0; extract = 1;
    }
    zin = fopen(argv[3], "r");
    if (zin == NULL) { perror("Opening input file"); exit(1); }
    fseek(zin, (long) BLKSIZ, SEEK_SET);

    /* Read in the tape directory entries */
    while (1) {
	tent = (struct tlist *) malloc(sizeof(struct tlist));
	tent->next = NULL;
	if ((fread(&(tent->tdir), sizeof(struct tapdir), 1, zin)) != 1) {
	    printf("fread failed on tap archive %s\n",argv[3]);
	    break;
	}

	/* If entry has no name, we've reached end of the list */
	if (tent->tdir.pathname[0] == '\0') break;

	/* Get the size of the file */
	size = tent->tdir.size;
	tent->size = size;

#ifdef BIGEND
	/* Convert multibyte fields */
	swap16(&(tent->tdir.size));
	swap16(&(tent->tdir.tapeaddr));
	swap16(&(tent->tdir.modtime));
	/* Convert the modification time into a normal Unix time value */
	b = (uint8_t *) & (tent->tdir.modtime);
	a = b[0]; b[0] = b[1]; b[1] = a;
	a = b[2]; b[2] = b[3]; b[3] = a;
#else
	/* Convert the modification time into a normal Unix time value */
	b = (uint8_t *) & (tent->tdir.modtime);
	a = b[0]; b[0] = b[2]; b[2] = a;
	a = b[1]; b[1] = b[3]; b[3] = a;
#endif
	/* Convert to seconds since epoch */
	modtime= (tent->tdir.modtime / 60) + (epoch * 365 * 24 * 60 * 60);

	/* Print the table of contents */
	if (toc) {
	    strcpy(modstr, "s------");
	    if (tent->tdir.size >4095)		   modstr[0]='l';
	    if (tent->tdir.mode & V1_ST_SETUID)	   modstr[1]='u';
	    if (tent->tdir.mode & V1_ST_EXEC)	   modstr[2]='x';
	    if (tent->tdir.mode & V1_ST_OWNREAD)   modstr[3]='r';
	    if (tent->tdir.mode & V1_ST_OWNWRITE)  modstr[4]='w';
	    if (tent->tdir.mode & V1_ST_WRLDREAD)  modstr[5]='r';
	    if (tent->tdir.mode & V1_ST_WRLDWRITE) modstr[6]='w';

	    Ctime=ctime((time_t *)&modtime); Ctime[strlen(Ctime)-1]='\0';

	    printf("%s %3d %5d %4d %s  %s\n",
		   modstr, tent->tdir.uid, size, tent->tdir.tapeaddr,
		   Ctime, tent->tdir.pathname);
	}

	/* Add entry to the list */
	if (thead == NULL) thead = this = tent;
	else { this->next = tent; this = tent; }
    }

    if (extract)
	for (this = thead; this; this = this->next) {

	    /* Print the file's name on stdout */
	    printf("x %s %d, ", this->tdir.pathname, this->size);

	    /* Convert file size into # of tape blocks */
	    blockbytes = BLKSIZ * ((this->size + 511) / 512);
	    printf("%d blockbytes\n", blockbytes);

	    /* Seek to the beginning of the file */
	    fseek(zin, BLKSIZ * this->tdir.tapeaddr, SEEK_SET);
	    size = this->size;

	    /* Open the output file */
	    pathname= this->tdir.pathname;
	    if (pathname[0]=='/') pathname++;
	    zout = fopen(pathname, "w");
	    if (zout == NULL) {
		/* Ok, try making the directories */
		mkrecursdir(pathname);
		zout = fopen(pathname, "w");
		if (zout == NULL) {
		    printf("Can't open %s for writing\n", pathname);
		    continue;
		}
	    }
	    
	    /* Read and write the blocks from the archive to the file */
	    while (size) {
		if ((fread(&buf, BLKSIZ, 1, zin)) != 1) {
		    printf("Error reading block for %s\n", pathname);
		    break;
		}
		if (size < BLKSIZ) { fwrite(&buf, size, 1, zout); size = 0; }
		else { fwrite(&buf, BLKSIZ, 1, zout); size -= BLKSIZ; }
	    }

	    /* Close the file */
	    fclose(zout);

	    /* Set up the file's mode, owner, group and modification time */
	    mode=0;
	    if (this->tdir.mode & V1_ST_SETUID)	   mode |= S_ISUID;
	    if (this->tdir.mode & V1_ST_EXEC) mode |= S_IXUSR|S_IXGRP|S_IXOTH;
	    if (this->tdir.mode & V1_ST_OWNREAD)   mode |= S_IRUSR;
	    if (this->tdir.mode & V1_ST_OWNWRITE)  mode |= S_IWUSR;
	    if (this->tdir.mode & V1_ST_WRLDREAD)  mode |= S_IRGRP | S_IROTH;
	    if (this->tdir.mode & V1_ST_WRLDWRITE) mode |= S_IWGRP | S_IWOTH;
	    chmod(pathname, mode);
	    chown(pathname, this->tdir.uid, 0);

	    /* Convert to seconds since 1970 */
	    modtime= (this->tdir.modtime / 60) + (epoch * 365 * 24 * 60 * 60);
	    utbuf.actime = utbuf.modtime = modtime;
	    utime(pathname, &utbuf);
	}
    exit(0);
}

/* Build the directory needed to create the file.
 * We cheat by using mkdir -p.
 */
void 
mkrecursdir(char *name)
{
    char *c;
    char buf[300];

    c = strrchr(name, '/');
    if (c) {
	*c = '\0';
	sprintf(buf, "/bin/mkdir -p %s", name); system(buf);
	*c = '/';
    }
}
