/*----------------------------------------------------------------------*/
/*                        LHarc Add Command                             */
/*                This is part of LHarc UNIX Archiver Driver            */
/*                                                                      */
/*                Copyright(C) MCMLXXXIX  Yooichi.Tagawa                */
/*                                                                      */
/*  V0.00  Original                             1988.05.23  Y.Tagawa    */
/*  V1.00  Fixed                                1989.09.22  Y.Tagawa    */
/*  V1.02  Bug fix                              1990.01.19  Y.Tagawa    */
/*  V0.03  LHa for UNIX                         1991.12.05  M.Oki       */
/*----------------------------------------------------------------------*/

#include "lharc.h"

extern int encode_lzhuf ();
extern int encode_stored_crc ();

static char new_archive_name_buffer [ FILENAME_LENGTH ];
static char *new_archive_name;
FILE *temporary_fp = NULL;

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




static void
add_one (fp, nafp, hdr)
FILE *fp, *nafp;
LzHeader *hdr;
{
    long header_pos, next_pos, org_pos, data_pos;
    long v_original_size, v_packed_size;

    reading_filename = hdr->name;
    writting_filename = temporary_name;

    if (!fp && generic_format)    /* [generic] doesn't need directory info. */
        return;
    header_pos = ftell (nafp);
    write_header (nafp, hdr);     /* DUMMY */

    if (hdr->original_size == 0)  /* empty file or directory */
        return;                 /* previous write_header is not DUMMY. (^_^) */

    org_pos = ftell (fp);
    data_pos = ftell (nafp);

    hdr->crc = encode_lzhuf (fp, nafp, hdr->original_size,
        &v_original_size, &v_packed_size, hdr->name, hdr->method);

    if (v_packed_size < v_original_size)
    {
        next_pos = ftell (nafp);
    }
    else
    {                                /* retry by stored method */
        fseek (fp, org_pos, SEEK_SET);
        fseek (nafp, data_pos, SEEK_SET);
        hdr->crc = encode_stored_crc (fp, nafp, hdr->original_size,
        &v_original_size, &v_packed_size);
        fflush (nafp);
        next_pos = ftell (nafp);
#ifndef NOFTRUNCATE
        ftruncate (fileno (nafp), next_pos);
#endif
        bcopy (LZHUFF0_METHOD, hdr->method, METHOD_TYPE_STRAGE);
    }
    hdr->original_size = v_original_size;
    hdr->packed_size = v_packed_size;
    fseek (nafp, header_pos, SEEK_SET);
    write_header (nafp, hdr);
    fseek (nafp, next_pos, SEEK_SET);
}


FILE *
append_it (name, oafp, nafp)
char *name;
FILE *oafp, *nafp;
{
    LzHeader ahdr, hdr;
    FILE *fp;
    long old_header;
    int cmp;
    int filec;
    char **filev;
    int i;
    STAT stbuf;
    boolean directory;

    if (stat (name, &stbuf) < 0)
    {
        error ("cannot access", name); /* See cleaning_files, Why? */
        return oafp;
    }

    directory = is_directory (&stbuf);

    init_header (name, &stbuf, &hdr);

    if (!directory && !noexec)
        fp = xfopen (name, READ_BINARY);
    else
        fp = NULL;

    while (oafp)
    {
        old_header = ftell (oafp);
        if (!get_header (oafp, &ahdr))
        {
            fclose (oafp);
            oafp = NULL;
            break;
        }
        else
        {
            cmp = STRING_COMPARE (ahdr.name, hdr.name);
            if (cmp < 0)
            {                        /* SKIP */
                /* copy old to new */
                if (!noexec)
                {
                    fseek (oafp, old_header, SEEK_SET);
                    copy_old_one (oafp, nafp, &ahdr);
                }
                else
                    fseek (oafp, ahdr.packed_size, SEEK_CUR);
            }
            else if (cmp == 0)
            {                        /* REPLACE */
                /* drop old archive's */
                fseek (oafp, ahdr.packed_size, SEEK_CUR);
                break;
            }
            else                        /* cmp > 0, INSERT */
            {
                fseek (oafp, old_header, SEEK_SET);
                break;
            }
        }
    }

    if (update_if_newer)
    {        /* not in archive || last_modified after copy in the archive */
        if ( (! oafp) || (cmp > 0) || \
            (ahdr.unix_last_modified_stamp < hdr.unix_last_modified_stamp))
        {
            if (noexec)
            {
#ifdef DEBUG
                printf ("ADD %s UPDATING - CMP = %d oafp = %s\n",
                    name, cmp, (oafp) ? "OPEN" : "CLOSED");
                printf ("Old Date: %sNew Date: %s",
                    ctime(&ahdr.unix_last_modified_stamp), /* archive's stamp */
                    ctime(&hdr.unix_last_modified_stamp));
                printf ("Old Time: 0x%0.08X\nNew Time: 0x%0.08X\n",
                    ahdr.unix_last_modified_stamp, /* archive's stamp */
                    hdr.unix_last_modified_stamp);
#else
                printf ("ADD %s\n", name);
#endif
            }
            else
                add_one (fp, nafp, &hdr);
        }
        else /* cmp == 0 */
        {                        /* copy old to new */
            if (!noexec)
            {
                fseek (oafp, old_header, SEEK_SET);
                copy_old_one (oafp, nafp, &ahdr);
            }
        }
    }
    else
    {
        if (!oafp || cmp > 0)        /* not in archive or dropped */
        {
            if (noexec)
                printf ("ADD %s\n", name);
            else
                add_one (fp, nafp, &hdr);
        }
        else /* cmp == 0 */        /* replace */
        {
            if (noexec)
                printf ("REPLACE\n");
            else
                add_one (fp, nafp, &hdr);
        }
    }

    if (!directory)
    {
        if (!noexec)
            fclose (fp);
    }
    else
        {                        /* recurcive call */
        if (find_files (name, &filec, &filev))
        {
            for (i = 0; i < filec; i ++)
                oafp = append_it (filev[i], oafp, nafp);
            free_files (filec, filev);
        }
    }
    return oafp;
}

static void
find_update_files (oafp)
FILE *oafp;                /* old archive */
{
    char name[FILENAME_LENGTH];
    struct string_pool sp;
    LzHeader hdr;
    long pos;
    STAT stbuf;
    int len;

    pos = ftell (oafp);

    init_sp (&sp);
    while (get_header (oafp, &hdr))
    {
#ifdef OSK
        if ((hdr.unix_mode & OSK_DIRECTORY_PERM) != OSK_DIRECTORY_PERM)
#else
        if ((hdr.unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_REGULAR)
#endif
        {
            if (stat (hdr.name, &stbuf) >= 0) /* exist ? */
                add_sp (&sp, hdr.name, strlen (hdr.name) + 1);
        }
#ifdef OSK
        else if ((hdr.unix_mode & OSK_DIRECTORY_PERM) == OSK_DIRECTORY_PERM)
#else
        else if ((hdr.unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_DIRECTORY)
#endif
        {
            strcpy (name, hdr.name);
            len = strlen (name);
            if (len > 0 && name[len - 1] == '/')
                name[--len] = '\0'; /* strip tail '/' */
            if (stat (name, &stbuf) >= 0) /* exist ? */
                add_sp (&sp, name, len+1);
        }
        fseek (oafp, hdr.packed_size, SEEK_CUR);
    }

    fseek (oafp, pos, SEEK_SET);

    finish_sp (&sp, &cmd_filec, &cmd_filev);
}

static void
delete (oafp, nafp)
FILE *oafp, *nafp;
{
    LzHeader ahdr;
    long old_header_pos;

    old_header_pos = ftell (oafp);
    while (get_header (oafp, &ahdr))
    {
        if (need_file (ahdr.name))
        {                        /* skip */
            fseek (oafp, ahdr.packed_size, SEEK_CUR);
            if (noexec)
                printf ("DELETE %s\n", ahdr.name);
            else if (verbose)
                printf ("Delete %s\n", ahdr.name);
        }
        else
            {                        /* copy */
            if (noexec)
            {
                fseek (oafp, ahdr.packed_size, SEEK_CUR);
            }
            else
                {
                fseek (oafp, old_header_pos, SEEK_SET);
                copy_old_one (oafp, nafp, &ahdr);
            }
        }
        old_header_pos = ftell (oafp);
    }
    return;
}


/*----------------------------------------------------------------------*/
/*                                                                      */
/*----------------------------------------------------------------------*/
static FILE *
build_temporary_file ()
{
    int old_umask;
    FILE *afp;

    build_temporary_name ();
    signal (SIGINT, interrupt);
    signal (SIGHUP, interrupt);
#ifndef OSK
    old_umask = umask (077);
#endif
    afp = xfopen (temporary_name, WRITE_BINARY);
    remove_temporary_at_error = TRUE;
    temporary_fp = afp;
#ifndef OSK
    umask (old_umask);
#endif
    return afp;
}

static void
build_backup_file ()
{

    build_backup_name (backup_archive_name, archive_name);
    if (!noexec)
    {
        signal (SIGINT, SIG_IGN);
        signal (SIGHUP, SIG_IGN);
        /* if old backup ".bak" archive exists, delete it first */
        if (!access(backup_archive_name,0))
            unlink(backup_archive_name);
        if (rename (archive_name, backup_archive_name) < 0)
            fatal_error (archive_name);
        recover_archive_when_interrupt = TRUE;
        signal (SIGINT, interrupt);
        signal (SIGHUP, interrupt);
    }
}

static void
report_archive_name_if_different ()
{
    if (!quiet && new_archive_name == new_archive_name_buffer)
    {
        /* warning at old archive is SFX */
        printf ("New archive file is \"%s\"\n", new_archive_name);
    }
}

#ifdef TMP_FILENAME_TEMPLATE
void
temporary_to_new_archive_file (new_archive_size)
long new_archive_size;
{
    FILE *oafp, *nafp;

    oafp = xfopen (temporary_name, READ_BINARY);
    if (!strcmp(new_archive_name, "-"))
    {
        nafp = stdout;
        writting_filename = "standard output";
    }
    else
        {
        nafp = xfopen (new_archive_name, WRITE_BINARY);
        writting_filename = archive_name;
    }
    reading_filename = temporary_name;
    copyfile (oafp, nafp, new_archive_size, 0);
    if (nafp != stdout) fclose (nafp);
    fclose (oafp);

    recover_archive_when_interrupt = FALSE;
    unlink (temporary_name);

    remove_temporary_at_error = FALSE;
}
#else
temporary_to_new_archive_file (new_archive_size)
long new_archive_size;
{
    char *p;
    p = (char *)rindex(new_archive_name,'/');
    p = p ? p+1 : new_archive_name;
    unlink ( new_archive_name );
    if ( rename ( temporary_name , p )<0 ) {
        fprintf(stderr, "Can't rename temporary_name '%s'\n", new_archive_name);
        exit(1);
    }
}
#endif

static void
set_archive_file_mode ()
{
    int umask_value;
    STAT stbuf;
#ifndef OSK
    if (archive_file_gid < 0)
    {
        umask (umask_value = umask (0));
        archive_file_mode = (~umask_value) & 0666; /* rw-rw-rw- */
        if (stat (".", &stbuf) >= 0)
            archive_file_gid = stbuf.st_gid;
    }
    if (archive_file_gid >= 0)
        chown (new_archive_name, getuid (), archive_file_gid);

    chmod (new_archive_name, archive_file_mode);
#endif
}


/*----------------------------------------------------------------------*/
/*                REMOVE FILE/DIRECTORY                                 */
/*----------------------------------------------------------------------*/

static void remove_files ();

static void
remove_one (name)
char *name;
{
    STAT stbuf;
    int filec;
    char **filev;

    if (stat (name, &stbuf) < 0)
    {
        warning ("Cannot access", name);
    }
    else if (is_directory (&stbuf))
    {
        if (find_files (name, &filec, &filev))
        {
            remove_files (filec, filev);
            free_files (filec, filev);
        }
        else
            warning ("Cannot open directory", name);

        if (noexec)
            printf ("REMOVE DIRECTORY %s\n", name);
#ifndef OSK
        else if (rmdir (name) < 0)
            warning ("Cannot remove directory", name);
        else if (verbose)
            printf ("Removed %s.\n", name);
#endif /* OSK */
    }
    else if (is_regularfile (&stbuf))
    {
        if (noexec)
            printf ("REMOVE FILE %s.\n", name);
        else if (unlink (name) < 0)
            warning ("Cannot remove", name);
        else if (verbose)
            printf ("Removed %s.\n", name);
    }
    else
        {
        error ("Cannot remove (not a file or directory)", name);
    }
}

static void
remove_files (filec, filev)
int filec;
char **filev;
{
    int i;

    for (i = 0; i < filec; i++)
        remove_one (filev[i]);
}

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

void
cmd_add ()
{
    LzHeader ahdr;
    FILE *oafp, *nafp;
    int i;
    long old_header;
    boolean old_archive_exist;
    long new_archive_size;

    /* exit if no operation */
    if (!update_if_newer && cmd_filec == 0)
    {
        error ("No files given in argument, do nothing.", "");
        return;
    }

    /* open old archive if exist */
    if ((oafp = open_old_archive ()) == NULL)
        old_archive_exist = FALSE;
    else
        old_archive_exist = TRUE;

    if (update_if_newer && cmd_filec == 0 && !oafp)
        fatal_error (archive_name); /* exit if cannot execute automatic update */
    errno = 0;

    if (new_archive && old_archive_exist)
    {
        fclose (oafp);
        oafp = NULL;
    }

    if (oafp && archive_is_msdos_sfx1 (archive_name))
    {
        skip_msdos_sfx1_code (oafp);
        build_standard_archive_name (new_archive_name_buffer, archive_name);
        new_archive_name = new_archive_name_buffer;
    }
    else
        {
        new_archive_name = archive_name;
    }

    /* build temporary file */
    if (!noexec)
        nafp = build_temporary_file ();

    /* find needed files when automatic update */
    if (update_if_newer && cmd_filec == 0)
        find_update_files (oafp);

    /* build new archive file */
    /* cleaning arguments */
    cleaning_files (&cmd_filec, &cmd_filev);
    if (cmd_filec == 0)
    {
        if (oafp)
            fclose (oafp);
        if  (!noexec)
            fclose (nafp);
        /* added unlink to get rid of temp file */
        unlink (temporary_name);

        return;
    }

    for (i = 0; i < cmd_filec; i ++)
        oafp = append_it (cmd_filev[i], oafp, nafp);
    if (oafp)
    {
        old_header = ftell (oafp);
        while (get_header (oafp, &ahdr))
        {
            if (noexec)
                fseek (oafp, ahdr.packed_size, SEEK_CUR);
            else
                {
                fseek (oafp, old_header, SEEK_SET);
                copy_old_one (oafp, nafp, &ahdr);
            }
            old_header = ftell (oafp);
        }
        fclose (oafp);
    }
    if (!noexec)
    {
        write_archive_tail (nafp);
        new_archive_size = ftell (nafp);
        fclose (nafp);
    }

    /* build backup archive file */
    if (old_archive_exist)
        build_backup_file ();

    report_archive_name_if_different ();

    /* copy temporary file to new archive file */
    if (!noexec && (!strcmp(new_archive_name, "-") ||
        rename (temporary_name, new_archive_name) < 0))
        temporary_to_new_archive_file (new_archive_size);

    /* set new archive file mode/group */
    set_archive_file_mode ();

    /* remove archived files */
    if (delete_after_append)
        remove_files (cmd_filec, cmd_filev);

    return;
}


void
cmd_delete ()
{
    FILE *oafp, *nafp;
    long new_archive_size;

    /* open old archive if exist */
    if ((oafp = open_old_archive ()) == NULL)
        fatal_error (archive_name);
    errno = 0;

    /* exit if no operation */
    if (cmd_filec == 0)
    {
        fclose (oafp);
        warning ("No files given in argument, do nothing.", "");
        return;
    }

    if (archive_is_msdos_sfx1 (archive_name))
    {
        skip_msdos_sfx1_code (oafp);
        build_standard_archive_name (new_archive_name_buffer, archive_name);
        new_archive_name = new_archive_name_buffer;
    }
    else
        {
        new_archive_name = archive_name;
    }

    /* build temporary file */
    if (!noexec)
        nafp = build_temporary_file ();

    /* build new archive file */
    delete (oafp, nafp);
    fclose (oafp);

    if (!noexec)
    {
        write_archive_tail (nafp);
        new_archive_size = ftell (nafp);
        fclose (nafp);
    }

    /* build backup archive file */
    build_backup_file ();

    report_archive_name_if_different ();

    /* copy temporary file to new archive file */
    if (!noexec && rename (temporary_name, new_archive_name) < 0)
        temporary_to_new_archive_file (new_archive_size);

    /* set new archive file mode/group */
    set_archive_file_mode ();

    return;
}
