#include "lharc.h"

/*----------------------------------------------------------------------*
 *                PROGRAM                                               *
 *----------------------------------------------------------------------*/

#define CMD_UNKNOWN    0
#define CMD_EXTRACT    1
#define CMD_ADD        2
#define CMD_LIST       3
#define CMD_DELETE     4

static	int	cmd 	= CMD_UNKNOWN;
	char    **cmd_filev;
	int     cmd_filec;

	char    *archive_name;
	char    expanded_archive_name[FILENAME_LENGTH];
	char    temporary_name[FILENAME_LENGTH];
	char    backup_archive_name[FILENAME_LENGTH];

/* static functions */

static 	void 	sort_files();

/* options */

	boolean	quiet		= FALSE;
	boolean	text_mode	= FALSE;
	boolean	verbose		= FALSE;
	boolean	noexec		= FALSE;    /* debugging option */
	boolean	force		= FALSE;
	boolean	prof		= FALSE;
	int	compress_method = 5;  	    /* default -lh5- */
	int 	header_level 	= HEADER_LEVEL1;
	


/* view command flags */
	boolean	verbose_listing		= FALSE;

/* extract command flags */
	boolean	output_to_stdout	= FALSE;

/* append command flags */
	boolean    new_archive = FALSE;
	boolean    update_if_newer = FALSE;
	boolean    delete_after_append = FALSE;
	boolean    generic_format = FALSE;

	boolean    remove_temporary_at_error = FALSE;
	boolean    recover_archive_when_interrupt = FALSE;
	boolean    remove_extracting_file_when_interrupt = FALSE;
	boolean    get_filename_from_stdin = FALSE;
	boolean    ignore_directory = FALSE;
	boolean    verify_mode = FALSE;

/* recursion switch */


	char *extract_directory = NULL;
	char *xfilev[257];

/*----------------------------------------------------------------------*/
/* NOTES :  Text File Format                                            */
/*    GENERATOR       NewLine                                           */
/*    [generic]       0D 0A                                             */
/*    [MS-DOS]        0D 0A                                             */
/*    [OS9][MacOS]    0D                                                */
/*    [UNIX]          0A                                                */
/*----------------------------------------------------------------------*/


static void print_tiny_usage_and_exit ()
{


    fprintf (stderr, "LHA Vrs. 2.06 for Amiga - revised Feb. 12, 1996  Cray-1\n");
    fprintf (stderr, "Syntax: LHa -{axelvudmcp}[qvnfodiszrg012][w=<dir>] archive_file [file...]\n");
    fprintf (stderr, "Function: Archive Management Utility\n");
    fprintf (stderr, "Commands:                           Options:\n");
    fprintf (stderr, " a   Add (create/replace) to archive q  quiet\n");
    fprintf (stderr, " x,e EXtract from archive            v  verbose\n");
    fprintf (stderr, " l,v List / Verbose List             n  not execute\n");
    fprintf (stderr, " u   Update newer files to archive   f  force (over write at extract)\n");
    fprintf (stderr, " d   Delete from archive             t  FILES are TEXT file\n");
    fprintf (stderr, " m   Move to archive (means 'ad')    o  use LHarc compatible method (a/u)\n");
    fprintf (stderr, " c   re-Construct new archive        w=<dir> specify extract directory (x/e)\n");
    fprintf (stderr, " p   Print to STDOUT from archive    d  delete FILES after (a/u/c)\n");
    fprintf (stderr, " t   Test file CRC in archive        i  ignore directory path (x/e)\n");
    fprintf (stderr, "                                     g  [Generic] format (for compatiblity)\n");
    fprintf (stderr, "                                     0/1/2 header level (a/u)\n");
    fprintf (stderr, "                                     s  store - don't compress files (a/u)\n");
    fprintf (stderr, "                                     z  get list of file from standard input\n");
    fprintf (stderr, "                                     r  Recursive expansion of dirs (a/u)\n\n");
    fprintf (stderr,"The environment variable TMPDIR may be set to specify the directory used for\n");
    fprintf (stderr,"creating temporary files while archiving\n");
    exit (1);
}

void main (int argc, char *argv[])
{
    char *p , inpbuf[256];

    if (argc < 2)
        print_tiny_usage_and_exit ();
    if (argc < 3) {
        if (argv[1][0] == '-' && argv[1][1] == '?')
            print_tiny_usage_and_exit ();
        cmd = CMD_LIST;
        argv--;
        argc++;
        goto work;
    }
    if ( argv[1][0]=='-' ) argv[1]++;
    /* commands */
    switch ( argv[1][0] )
    {
    case 'x':
    case 'e':
        cmd = CMD_EXTRACT;
        break;

    case 'p':
        output_to_stdout = TRUE;
        cmd = CMD_EXTRACT;
        break;

    case 'c':
        new_archive = TRUE;
        cmd = CMD_ADD;
        break;

    case 'a':
        cmd = CMD_ADD;
        break;

    case 'd':
        cmd = CMD_DELETE;
        break;

    case 'u':
        update_if_newer = TRUE;
        cmd = CMD_ADD;
        break;

    case 'm':
        delete_after_append = TRUE;
        cmd = CMD_ADD;
        break;

    case 'v':
        verbose_listing = TRUE;
        cmd = CMD_LIST;
        break;

    case 'l':
        cmd = CMD_LIST;
        break;

    case 't':
        cmd = CMD_EXTRACT;
        verify_mode = TRUE;
        break;

    default:
        print_tiny_usage_and_exit ();

    }

    /* options */
    p = &argv[1][1];
    for (p = &argv[1][1]; *p; )
    {
        switch ( (*p++) )
        {
        case 'q':    
            quiet = TRUE; 
            break;
        case 'f':    
            force = TRUE; 
            break;
        case 'p':    
            prof = TRUE; 
            break;
        case 'v':    
            verbose = TRUE; 
            break;
        case 't':    
            text_mode = TRUE; 
            break;

        case 'n':    
            noexec = TRUE; 
            break;
        case 'g':    
            generic_format = TRUE; 
            header_level = 0; 
            break;
        case 'd':    
            delete_after_append = TRUE; 
            break;
        case 'o':    
            compress_method = 1; 
            header_level = 0; 
            break;
        case 'z':    

            break;
        case 'i':    
            ignore_directory = TRUE; 
            break;

        case 'w':    
            if ( *p=='=' ) p++;
            extract_directory=p;
            while (*p) p++;
            break;
        case '0':    
            header_level = HEADER_LEVEL0; 
            break;
        case '1':    
            header_level = HEADER_LEVEL1; 
            break;
        case '2':    
            header_level = HEADER_LEVEL2; 
            break;
        default:
            fprintf(stderr, "LHa: Unknown option '%c'.\n", p[-1]);
            exit(1);
        }
    }

work:
    /* archive file name */
    archive_name = argv[2];

    if (!strcmp(archive_name, "-"))
    {
        if (!isatty(1) && cmd == CMD_ADD) quiet = TRUE;
    }
    else
    {
        if (argc == 3 && !isatty(0))
            get_filename_from_stdin = TRUE;
    }

    /* target file name */
    if ( get_filename_from_stdin )
    {
        cmd_filec = 0;
        while ( gets( inpbuf ) )
        {
            if ( strlen( inpbuf )<1 ) continue;
            if ( (xfilev[cmd_filec++]=(char *)strdup(inpbuf))==NULL )
                fatal_error("Virtual memory exhausted\n");
            if ( cmd_filec>256 )
                fatal_error("Too many file names\n");
        }
        xfilev[cmd_filec] = NULL;
        cmd_filev = xfilev;
    }
    else
    {
        cmd_filec = argc - 3;
        cmd_filev = argv + 3;
    }
    sort_files ();

    /* make crc table */
    make_crctable();

    switch (cmd)
    {
    case CMD_EXTRACT:    
        cmd_extract ();    
        break;
    case CMD_ADD:    
        cmd_add ();    
        break;
    case CMD_LIST:    
        cmd_list ();    
        break;
    case CMD_DELETE:    
        cmd_delete ();    
        break;
    }



    exit (0);
}

static void
message_1 (title, subject, name)
char *title, *subject, *name;
{
    fprintf (stderr, "LHa: %s%s ", title, subject);
    fflush (stderr);

    if (errno == 0)
        fprintf (stderr, "%s\n", name);
    else
        perror (name);
}

void
message (subject, name)
char *subject, *name;
{
    message_1 ("", subject, name);
}

void
warning (subject, name)
char *subject, *name;
{
    message_1 ("Warning: ", subject, name);
}

void
error (subject, msg)
char *subject, *msg;
{
    message_1 ("Error: ", subject, msg);
}

void
fatal_error (msg)
char *msg;
{
    message_1 ("Fatal error:", "", msg);

    if (remove_temporary_at_error)
        unlink (temporary_name);

    exit (1);
}

char *writting_filename;
char *reading_filename;

void
write_error ()
{
    fatal_error (writting_filename);
}

void
read_error ()
{
    fatal_error (reading_filename);
}

void
interrupt (signo)
int signo;
{
    errno = 0;
    message ("Interrupted\n", "");

    if ( temporary_fp ) fclose (temporary_fp);
    unlink (temporary_name);
    if (recover_archive_when_interrupt)
        rename (backup_archive_name, archive_name);
    if (remove_extracting_file_when_interrupt)
    {
        errno = 0;
        message ("Removing", writting_filename);
        unlink (writting_filename);
    }
    signal (SIGINT, SIG_DFL);
    
    kill (getpid (), signo);
}



/*----------------------------------------------------------------------*/
/*                                                                      */
/*----------------------------------------------------------------------*/

static int
sort_by_ascii (a, b)
char **a, **b;
{
    register char *p, *q;
    register int c1, c2;

    p = *a, q = *b;
    if (generic_format)
    {
        do
        {
            c1 = *(unsigned char*)p ++;
            c2 = *(unsigned char*)q ++;
            if (!c1 || !c2)
                break;
            if (islower (c1))
                c1 = toupper (c1);
            if (islower (c2))
                c2 = toupper (c2);
        }
        while (c1 == c2) ;
        return c1 - c2;
    }
    else
        {
        while (*p == *q && *p != '\0')
            p ++, q ++;
        return *(unsigned char*)p - *(unsigned char*)q;
    }
}

static void
sort_files ()
{
    if (cmd_filec > 1)
        qsort (cmd_filev, cmd_filec, sizeof (char*), sort_by_ascii);
}

char *xmalloc (size)
int size;
{
    char *p = (char *)malloc (size);
    if (!p)
        fatal_error ("Not enough memory");
    return p;
}

char *xrealloc (old, size)
char *old;
int size;
{
    char *p = (char *)realloc (old, size);
    if (!p)
        fatal_error ("Not enough memory");
    return p;
}

/*----------------------------------------------------------------------*/
/*                STRING POOL                                           */
/*----------------------------------------------------------------------*/

/*
 * string pool :
 *    +-------------+-------------+---     ---+-------------+----------+
 *    | N A M E 1 \0| N A M E 2 \0|    ...    | N A M E n \0|          |
 *    +-------------+-------------+---     ---+-------------+----------+
 *    ^                              ^         ^
 * buffer+0                        buffer+used  buffer+size
 */

/*
 * vector :
 *    +---------------+---------------+-------------     -------------+
 *    | pointer to    | pointer to    | pointer to   ...  pointer to  |
 *    | string pool   |  N A M E 1    |  N A M E 2   ...   N A M E n  |
 *    +---------------+---------------+-------------     -------------+
 *    ^        ^
 *   malloc base      returned
 */

void
init_sp (sp)
struct string_pool *sp;
{
    sp->size = 1024 - 8;        /* any ( >=0 ) */
    sp->used = 0;
    sp->n = 0;
    sp->buffer = (char*)xmalloc (sp->size * sizeof (char));
}

void
add_sp (sp, name, len)
struct string_pool *sp;
char *name;        /* stored '\0' at tail */
int len;            /* include '\0' */
{
    while (sp->used + len > sp->size)
    {
        sp->size *= 2;
        sp->buffer = (char*) xrealloc (sp->buffer, sp->size * sizeof (char));
    }
    bcopy (name, sp->buffer + sp->used, len);
    sp->used += len;
    sp->n ++;
}

void
finish_sp (sp, v_count, v_vector)
register struct string_pool *sp;
int *v_count;
char ***v_vector;
{
    int i;
    register char *p;
    char **v;

    v = (char**) xmalloc ((sp->n + 1) * sizeof (char*));
    *v++ = sp->buffer;
    *v_vector = v;
    *v_count = sp->n;
    p = sp->buffer;
    for (i = sp->n; i; i --)
    {
        *v++ = p;
        if (i - 1)
            p += strlen (p) + 1;
    }
}

void
free_sp (vector)
char **vector;
{
    vector --;
    free (*vector);        /* free string pool */
    free (vector);
}


/*----------------------------------------------------------------------*/
/*            READ DIRECTORY FILES                                      */
/*----------------------------------------------------------------------*/

static boolean
include_path_p (path, name)
char *path, *name;
{
    char *n = name;
    while (*path)
        if (*path++ != *n++)
            return (path[-1] == '/' && *n == '\0');
    return (*n == '/' || (n != name && path[-1] == '/' && n[-1] == '/'));
}

#define STREQU(a,b)    (((a)[0] == (b)[0]) ? (strcmp ((a),(b)) == 0) : FALSE)
void
cleaning_files (v_filec, v_filev)
int *v_filec;
char ***v_filev;
{
    char *flags;
    char tmp_back_name[FILENAME_LENGTH];
    STAT stbuf;
    register char **filev = *v_filev;
    register int filec = *v_filec;
    register char *p;
    register int i, j;
    void build_backup_name ();

    if (filec == 0)
        return;

    flags = xmalloc (filec * sizeof (char));

    /* flags & 0x01 :    1: ignore */
    /* flags & 0x02 :    1: directory, 0 : regular file */
    /* flags & 0x04 :    1: need delete */

    for (i = 0; i < filec; i ++)
        if (stat (filev[i], &stbuf) < 0)
        {
            flags[i] = 0x04;
            fprintf (stderr,
            "LHa: Cannot access \"%s\", ignored.\n", filev[i]);
        }
        else
        {

            if (is_regularfile (&stbuf))
                flags[i] = 0x00;
                flags[i] = 0x02;
            }
            else
            {
                flags[i] = 0x04;
                fprintf (stderr,
                "LHa: Cannot archive \"%s\", ignored.\n", filev[i]);
            }
        }
    errno = 0;

    for (i = 0; i < filec; i ++)
    {
        p = filev[i];
        if ((flags[i] & 0x07) == 0x00)
        {            /* regular file, not deleted/ignored */
            for (j = i + 1; j < filec; j ++)
            {
                if ((flags[j] & 0x07) == 0x00)
                {        /* regular file, not deleted/ignored */
                    if (STREQU (p, filev[j]))
                        flags[j] = 0x04; /* delete */
                }
            }
        }
        else if ((flags[i] & 0x07) == 0x02)
        {            /* directory, not deleted/ignored */
            for (j = i + 1; j < filec; j ++)
            {
                if ((flags[j] & 0x07) == 0x00)
                {        /* regular file, not deleted/ignored */
                    if (include_path_p (p, filev[j]))
                        flags[j] = 0x04; /* delete */
                }
                else if ((flags[j] & 0x07) == 0x02)
                {        /* directory, not deleted/ignored */
                    if (include_path_p (p, filev[j]))
                        flags[j] = 0x04; /* delete */
                }
            }
        }
    }

    for (i = j = 0; i < filec; i ++)
    {
        if ((flags[i] & 0x04) == 0)
        {
            if (i != j)
                filev[j] = filev[i];
            j ++;
        }
    }
    *v_filec = j;

    free (flags);
}

#ifdef NODIRECTORY
/* please need your imprementation */
boolean
find_files (name, v_filec, v_filev)
char *name;
int *v_filec;
char ***v_filev;
{
    return FALSE;            /* DUMMY */
}

void
free_files (filec, filev)
int filec;
char **filev;
{
    /* do nothing */
}
#else
boolean
find_files (name, v_filec, v_filev)
char *name;
int *v_filec;
char ***v_filev;
{
    struct string_pool sp;
    char newname[FILENAME_LENGTH];
    int len, n;
    DIR *dirp;
    DIRENTRY *dp;
    STAT tmp_stbuf, arc_stbuf, fil_stbuf;

    strcpy (newname, name);
    len = strlen (name);
    if (len > 0 && newname[len-1] != '/')
        newname[len++] = '/';

    dirp = opendir (name);
    if (!dirp)
        return FALSE;

    init_sp (&sp);

    GETSTAT(temporary_name, &tmp_stbuf);
    GETSTAT(archive_name, &arc_stbuf);

    for (dp = readdir (dirp); dp != NULL; dp = readdir (dirp))
    {
        n = NAMLEN (dp);
        strncpy (newname+len, dp->d_name, n);
        newname[len + n] = '\0';
        if (GETSTAT(newname, &fil_stbuf) < 0) continue;
        if ((dp->d_ino != 0) &&

            /* exclude '.' and '..' */
            ((dp->d_name[0] != '.') ||
            ((n != 1) &&
            ((dp->d_name[1] != '.') ||
            (n != 2)))) &&
            ((tmp_stbuf.st_dev != fil_stbuf.st_dev ||
            tmp_stbuf.st_ino != fil_stbuf.st_ino) &&
            (arc_stbuf.st_dev != fil_stbuf.st_dev ||
            arc_stbuf.st_ino != fil_stbuf.st_ino)))


        {
            add_sp (&sp, newname, len + n + 1);
        }
    }
    closedir (dirp);
    finish_sp (&sp, v_filec, v_filev);
    if (*v_filec > 1)
        qsort (*v_filev, *v_filec, sizeof (char*), sort_by_ascii);
    cleaning_files (v_filec, v_filev);

    return TRUE;
}

void
free_files (filec, filev)
int filec;
char **filev;
{
    free_sp (filev);
}
#endif

/*----------------------------------------------------------------------*/
/*                                    */
/*----------------------------------------------------------------------*/

/* Build temporary file name and store to TEMPORARY_NAME */
void
build_temporary_name ()
{
#ifdef TMP_FILENAME_TEMPLATE
#ifdef OSK
    char *tmpdir;
    int len;
    
    if (tmpdir = getenv("TMPDIR")) {
        strcpy(temporary_name,tmpdir);
        len = strlen(temporary_name);
        if (temporary_name[len - 1] != '/')
            strcat(temporary_name,"/");
    } else
#endif /* OSK */
        strcpy(temporary_name,"");
        

    /* "/tmp/lhXXXXXX" etc. */
    strcat (temporary_name, TMP_FILENAME_TEMPLATE);
    mktemp (temporary_name);
#else
    char *p, *s;

    strcpy (temporary_name, archive_name);
    for (p = temporary_name, s = (char*)0; *p; p ++)
        if (*p == '/')
            s = p;
    strcpy ((s ? s+1 : temporary_name), "lhXXXXXX");
    mktemp (temporary_name);
#endif
}

static void
modify_filename_extention (buffer, ext)
char *buffer;
char *ext;
{
    register char *p, *dot;

    for (p = buffer, dot = (char*)0; *p; p ++)
    {
        if (*p == '.')
            dot = p;
        else if (*p == '/')
            dot = (char*)0;
    }

    if (dot)
        p = dot;

    strcpy (p, ext);
}

/* build backup file name */
void
build_backup_name (buffer, original)
char *buffer;
char *original;
{
    strcpy (buffer, original);
    modify_filename_extention (buffer, BACKUPNAME_EXTENTION); /* ".bak" */
}

void
build_standard_archive_name (buffer, orginal)
char *buffer;
char *orginal;
{
    strcpy (buffer, orginal);
    modify_filename_extention (buffer, ARCHIVENAME_EXTENTION); /* ".lzh" */
}

/*----------------------------------------------------------------------*/
/*                                                                      */
/*----------------------------------------------------------------------*/

extern int patmatch();

boolean
need_file (name)
char *name;
{
    int i;

    if (cmd_filec == 0)
        return TRUE;

    for (i = 0; i < cmd_filec; i ++)
    {
        if (patmatch(cmd_filev[i], name, 0 ) )
            return TRUE;
    }

    return FALSE;
}

FILE *
xfopen (name, mode)
char *name, *mode;
{
    FILE *fp;

    if ((fp = fopen (name, mode)) == NULL)
        fatal_error (name);

    return fp;
}


/*----------------------------------------------------------------------*/
/*                                                                      */
/*----------------------------------------------------------------------*/
int    archive_file_mode;
int    archive_file_gid;

static boolean
open_old_archive_1 (name, v_fp)
char *name;
FILE **v_fp;
{
    FILE *fp;
    STAT stbuf;

    if (stat (name, &stbuf) >= 0 &&
        is_regularfile (&stbuf) &&
        (fp = fopen (name, READ_BINARY)) != NULL)
    {
        *v_fp = fp;
        archive_file_gid = stbuf.st_gid;
        archive_file_mode = stbuf.st_mode;
        return TRUE;
    }

    *v_fp = NULL;
    archive_file_gid = -1;
    return FALSE;
}

FILE *
open_old_archive ()
{
    FILE *fp;
    char *p;

    if (!strcmp(archive_name, "-"))
    {
        if (cmd == CMD_EXTRACT || cmd == CMD_LIST) return stdin;
        else return NULL;
    }
    if (p = (char *)rindex(archive_name,'.'))
    {
        if ( strucmp(".LZH",p)==0
            || strucmp(".LZS",p)==0
            || strucmp(".COM",p)==0        /* DOS SFX */
            || strucmp(".EXE",p)==0
            || strucmp(".X"  ,p)==0        /* HUMAN SFX */
            || strucmp(".BAK",p)==0 )      /* for BackUp */
        {
            open_old_archive_1 (archive_name, &fp );
            return fp;
        }
    }

    if ( open_old_archive_1 (archive_name, &fp) )
        return fp;
    sprintf( expanded_archive_name , "%s.lzh",archive_name);
    if ( open_old_archive_1 (expanded_archive_name, &fp) )
    {
        archive_name = expanded_archive_name;
        return fp;
    }
    /*  if ( (errno&0xffff)!=E_PNNF )
    {
        archive_name = expanded_archive_name;
        return NULL;
    }
    */
    sprintf( expanded_archive_name, "%s.lzs",archive_name);
    if ( open_old_archive_1 (expanded_archive_name, &fp ) )
    {
        archive_name = expanded_archive_name;
        return fp;
    }
    /*  if ( (errno&0xffff)!=E_PNNF )
    {
        archive_name = expanded_archive_name;
        return NULL;
    }
    */
    sprintf( expanded_archive_name , "%s.lzh",archive_name);
    archive_name = expanded_archive_name;
    return NULL;
}

int
inquire (msg, name, selective)
char *msg, *name, *selective;
{
    char buffer[1024];
    char *p;

    for (;;)
    {
        fprintf (stdout, "%s %s ", name, msg);
        fflush (stdout);

        fgets (buffer, 1024, stdin);

        for (p = selective; *p; p++)
            if (buffer[0] == *p)
                return p - selective;
    }
    /*NOTREACHED*/
}

void
write_archive_tail (nafp)
FILE *nafp;
{
    putc (0x00, nafp);
}

void
copy_old_one (oafp, nafp, hdr)
FILE *oafp, *nafp;
LzHeader *hdr;
{
    if (noexec)
    {
        fseek (oafp, (long)(hdr->header_size + 2) + hdr->packed_size, SEEK_CUR);
    }
    else
    {
        reading_filename = archive_name;
        writting_filename = temporary_name;
        copyfile (oafp, nafp, (long)(hdr->header_size + 2) + hdr->packed_size,0);
    }
}
