/*   
**   ttar.c trivial tar archiving utility
**
**   CSC209S Assignment #2 - Feb 29, 2000
**       Craig Armstrong
*/

#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
#include <limits.h>

#define MAXPATH 512
#define MAXARGS 25
#define MAXBUF 4096

const char *szHeader = "ttar1.0";

/* fatal: print error and stop */
void fatal(char *msg) {
    fprintf(stderr, "%s\n", msg);
    exit(1);
}

/* addItem:  add file or directory to archive */
void addItem(char *szItemPath, FILE *fpArchive);

/* addFile:  add file to archive */
void addFile( char *szFileName, FILE *fpArchive ) {
    struct stat statBuf, statBuf2;
    FILE *fpFile;
    unsigned long size, count;
    char buf[MAXBUF];

    /* Get file information */
    if(lstat(szFileName, &statBuf) >= 0)
        size=statBuf.st_size;
    else
        fatal("Unknown file size");

    /* Don't add file if ino matches archive file */
    fstat(fileno(fpArchive), &statBuf2);
    if(statBuf.st_ino == statBuf2.st_ino) return;

    /* Write size, path, and contents to archive */
    printf("   %s (%lu bytes)\n", szFileName, size);
    if((fpFile=fopen(szFileName, "r")) == NULL)
        fatal("Cannot read file");
    if(fwrite(&size, sizeof(size), 1, fpArchive) != 1)
        fatal("Error writing size to archive file");
    fputs(szFileName, fpArchive);
    fputc(0, fpArchive);  /* null to terminate szFileName */
    while( (count=fread(buf, 1, MAXBUF, fpFile)) )
        if(fwrite(buf, 1, count, fpArchive) != count)
            fatal("Error writing to archive file");
    fclose(fpFile);
}

/* addDir: add all items from directory to archive */
void addDir( char *szDirName, FILE *fpArchive ) {
    DIR *dp;
    struct dirent *d;
    char szItemPath[MAXPATH];

    /* Add each item in the directory to the archive */
    if((dp=opendir(szDirName)) == NULL)
        fatal("Cannot access directory");
    while((d=readdir(dp)))
        if(d->d_ino != 0 && d->d_name[0] != '.') {
            strcpy(szItemPath, szDirName);
            strcat(szItemPath, "/");
            strcat(szItemPath, d->d_name);
            addItem(szItemPath, fpArchive);
        }
    closedir(dp);
}

/* addItem:  add file or directory to archive */
void addItem(char *szItemPath, FILE *fpArchive) {
    struct stat statBuf;

    /* Add either a file or a directory */
    if(lstat(szItemPath, &statBuf) < 0) {
        printf("Can't add %s\n", szItemPath);
        return;
    }
    if(S_ISREG(statBuf.st_mode) && access(szItemPath,R_OK)==0)
        addFile(szItemPath, fpArchive);
    else
        if(S_ISDIR(statBuf.st_mode) && access(szItemPath,R_OK)==0)
            addDir(szItemPath, fpArchive);
        else
            printf("Skipping %s\n", szItemPath);
}

/* ttarCreate: create archive and add each specified item */
void ttarCreate(char* szArchiveName, int num, char *item[]) {
    FILE *fpArchive;
    int i;

    if(num==0) fatal("No files specified");

    /* Open archive file */
    if( (fpArchive=fopen(szArchiveName, "w")) == NULL )
        fatal("Cannot write archive file");
    fputs(szHeader, fpArchive);

    /* Add all specified items */
    printf("Adding files to %s\n", szArchiveName);
    for(i=0; i<num; i++) addItem(item[i], fpArchive);
    fclose(fpArchive);
}

/* ttarList: list contents of archive */
void ttarList(char *szArchiveName) {
    char nameBuffer[MAXPATH];
    FILE *fpArchive;
    unsigned long size;
    int i;

    /* Open archive file */
    if( (fpArchive=fopen(szArchiveName, "r")) == NULL )
        fatal("Cannot read archive file");
    if(fgets(nameBuffer, strlen(szHeader)+1, fpArchive) == NULL)
        fatal("Cannot read archive file header");
    if(strcmp(nameBuffer, szHeader) != 0 )
        fatal("Not a ttar archive file");

    /* Display each path in archive */
    printf("Archive contains:\n");
    while( !feof(fpArchive) ) {
        if( fread(&size, sizeof(size), 1, fpArchive) == 0 )
            fatal("Error reading file size from archive");
        i=0;
        while( i<MAXPATH && (nameBuffer[i++]=getc(fpArchive))>0 );            
        printf("   %s (%lu bytes)\n", nameBuffer, size);
        fseek(fpArchive, size, SEEK_CUR);  /* skip over file */
        /* Test file for more data */
        if( fgetc(fpArchive) >=0 ) fseek(fpArchive, -1, SEEK_CUR);
    }            
    fclose(fpArchive);
}

/* ttarExtract: extract contents of archive */
void ttarExtract(char *szArchiveName) {
    char nameBuffer[MAXPATH];
    FILE *fpArchive, *fpFile;
    unsigned long size, count;
    char buf[MAXBUF];
    int i;

    /* Open archive file */
    if( (fpArchive=fopen(szArchiveName, "r")) == NULL )
        fatal("Cannot read archive file");
    if(fgets(nameBuffer, strlen(szHeader)+1, fpArchive) == NULL)
        fatal("Cannot read archive file header");
    if(strcmp(nameBuffer, szHeader) != 0 )
        fatal("Not a ttar archive file");

    /* Get files from archive */
    printf("Extracting files from %s\n", szArchiveName);
    while( !feof(fpArchive) ) {
        if( fread(&size, sizeof(size), 1, fpArchive) == 0 )
            fatal("Error reading file size from archive");

        /* Get filename and create directory path */
        i=0;
        while( i<MAXPATH && (nameBuffer[i]=getc(fpArchive))>0 ) {
            if( i && nameBuffer[i]=='/') {
                nameBuffer[i]=0;
                /* ignore path creation errors until file create */
                mkdir(nameBuffer, 0755);
                nameBuffer[i]='/';
            }
            i++;
        }            

        /* Extract file contents */
        printf("   %s (%lu bytes)\n", nameBuffer, size);
        if( (fpFile=fopen(nameBuffer, "w")) == NULL )
            fatal("Cannot open destination file");
        while( (size) ) {
            if(size < MAXBUF) count=size;
            else              count=MAXBUF;

            if(fread(buf, 1, count, fpArchive) != count )
                    fatal("Error reading archive file");
            if(fwrite(buf, 1, count, fpFile) != count)
                fatal("Error writing file");
            size=size-count;
        }
        fclose(fpFile);
        /* Test file for more data */
        if( fgetc(fpArchive) >=0 ) fseek(fpArchive, -1, SEEK_CUR);
    }
    fclose(fpArchive);
}

/* showHelp:  display help text and exit */
void showHelp() {
    fprintf(stderr, "ttar 1.0 - Trivial tar utility\n");
    fprintf(stderr, "Usage:\n");
    fprintf(stderr, "    ttar -c <archive file> <path> [<path>]\n");
    fprintf(stderr, "    ttar -l <archive file>\n");
    fprintf(stderr, "    ttar -x <archive file>\n");
    exit(1);
}

int main(int argc, char *argv[]) {
    char *szOption, *szArchiveName;
    int option;

    /* Get command line option and archive name */
    if(argc<3) showHelp();
    szOption = argv[1];
    szArchiveName=argv[2];
    if(szOption[0]=='-')
        option=szOption[1];
    else
        showHelp();

    switch (option) {
    case 'c':          /* Create */
    case 'C':
        ttarCreate(szArchiveName, argc-3, &argv[3]);
        break;
    case 'l':          /* List */
    case 'L':
        ttarList(szArchiveName);
        break;
    case 'x':          /* Extract */
    case 'X':
        ttarExtract(szArchiveName);
        break;
    default:           /* unrecognized option */
        showHelp();
        break;
    }
    return 0;
}


