OSDN Git Service

pass the lha-test11
[lha/olha.git] / ar.c
diff --git a/ar.c b/ar.c
index fb0aae1..bbcd9b4 100644 (file)
--- a/ar.c
+++ b/ar.c
@@ -2,6 +2,8 @@
        ar.c -- main file
 ***********************************************************/
 
+static char *version = "0.01";
+
 static char *usage =
     "ar -- compression archiver -- written by Haruhiko Okumura\n"
     "  PC-VAN:SCIENCE        CompuServe:74050,1022\n"
@@ -45,21 +47,68 @@ Structure of archive block (low order byte first):
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>
+#include <errno.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <utime.h>
 #include "ar.h"
 
+extern char *basename(const char *);
+
+struct lha_method methods[] = {
+    /* id, dicbit, pbit, maxmatch */
+    /* note: dicbit == 0 means no compress */
+    {"-lh0-", 0,  0,  0},  /* 0: no compress */
+    {"-lh1-", 12, 0, 60},  /* 1: 2^12 =  4KB dynamic huffman (LHarc) */
+    {"-lh2-", 13, 0,256},  /* 2: 2^13 =  8KB dynamic huffman */
+    {"-lh3-", 13, 0,256},  /* 3: 2^13 =  8KB static huffman */
+    {"-lh4-", 12, 4,256},  /* 4: 2^12 =  4KB static huffman (pos and len)*/
+    {"-lh5-", 13, 4,256},  /* 5: 2^13 =  8KB static huffman (pos and len)*/
+    {"-lh6-", 15, 5,256},  /* 6: 2^15 = 32KB static huffman (pos and len)*/
+    {"-lh7-", 16, 5,256},  /* 7: 2^16 = 64KB static huffman (pos and len)*/
+    {"-lzs-", 11, 0, 17},  /* 8: 2^11 =  2KB (LArc) */
+    {"-lz5-", 12, 0, 17},  /* 9: 2^12 =  4KB (LArc) */
+    {"-lz4-", 0,  0,  0},  /* 1: no compress (LArc) */
+    {"-lhd-", 0,  0,  0},  /* 1: directory */
+};
+
+struct lha_opts opts;
+
+struct lha_method *
+which_method(char *id)
+{
+    int i;
+
+    for (i = 0; i < sizeof(methods)/sizeof(methods[0]); i++) {
+        if (strncmp(id, methods[i].id, sizeof(methods[0].id)) == 0) {
+            return &methods[i];
+        }
+    }
+    return NULL;
+}
+
 #define FNAME_MAX (255 - 25)    /* max strlen(filename) */
-#define namelen  header[19]
-#define filename ((char *)&header[20])
 
 int unpackable;                 /* global, set in io.c */
 ulong compsize, origsize;       /* global */
 
-static uchar buffer[DICSIZ];
-static uchar header[255];
-static uchar headersize, headersum;
-static uint file_crc;
 static char *temp_name;
 
+static void
+print_usage()
+{
+    printf("%s", usage);
+    exit(0);
+}
+
+static void
+print_version()
+{
+    printf("version %s\n", version);
+    exit(0);
+}
+
 static uint
 ratio(ulong a, ulong b)
 {                               /* [(1000a + [b/2]) / b] */
@@ -79,84 +128,800 @@ ratio(ulong a, ulong b)
     return (uint) ((a + (b >> 1)) / b);
 }
 
+static char*
+os_string(char id)
+{
+    int i;
+    static struct os {
+        char id;
+        char *name;
+    } os_types[] = {
+        {'M',  "MS-DOS"},       /* Microsoft */
+        {'U',  "Unix"},         /* Unix or POSIX compliant OS */
+        {'J',  "Java"},         /* Sun Microsystems */
+        {'\0', "generic"},
+        {'w',  "Win9x"},        /* reserved by UNLHA32.DLL */
+        {'W',  "WinNT"},        /* reserved by UNLHA32.DLL */
+        {'2',  "OS/2"},         /* IBM OS/2 */
+        {'9',  "OS9"},          /* unknown */
+        {'K',  "OS/68K"},       /* unknown */
+        {'3',  "OS/386"},       /* unknown */
+        {'H',  "Human"},        /* SHARP Human68K */
+        {'C',  "CP/M"},         /* Digital Research */
+        {'F',  "FLEX"},         /* unknown */
+        {'m',  "Mac"},          /* Apple */
+        {'R',  "Runser"},       /* unknown */
+        {'T',  "TownsOS"},      /* Fujitsu FM-TOWNS */
+        {'X',  "XOSK"},         /* unknown */
+    };
+
+    for (i = 0; i < sizeof(os_types)/sizeof(os_types[0]); i++) {
+        if (id == os_types[i].id)
+            return os_types[i].name;
+    }
+
+    return "Unknown";
+}
+
 static void
-put_to_header(int i, int n, ulong x)
+put_header(char *buf, int *i, int n, uint32_t x)
 {
     while (--n >= 0) {
-        header[i++] = (uchar) ((uint) x & 0xFF);
+        buf[(*i)++] = (uchar) (x & 0xFF);
         x >>= 8;
     }
 }
 
-static ulong
-get_from_header(int i, int n)
+static void
+put_header_tmp(char *buf, int i, int n, uint32_t x)
+{
+    put_header(buf, &i, n, x);
+}
+
+void
+put_string(char *buf, int *n, size_t size, char *p)
+{
+    memcpy(buf + *n, p, size);
+    *n += size;
+}
+
+static uint32_t
+get_header(char *buf, int *i, int size)
 {
-    ulong s;
+    uint32_t s;
+    int n;
 
     s = 0;
-    while (--n >= 0)
-        s = (s << 8) + header[i + n];   /* little endian */
+    for (n = size-1; n >= 0; n--) {
+        s = (s << 8) + (unsigned char)buf[*i + n];   /* little endian */
+    }
+    *i += size;
     return s;
 }
 
+void
+get_string(char *buf, int *i, size_t size, char *p)
+{
+    memcpy(p, buf + *i, size);
+    *i += size;
+}
+
 static uint
-calc_headersum(void)
+calc_headersum(char *buf, int size)
 {
     int i;
     uint s;
 
     s = 0;
-    for (i = 0; i < headersize; i++)
-        s += header[i];
+    for (i = 0; i < size; i++)
+        s += buf[i];
     return s & 0xFF;
 }
 
+#if 0
+int
+get_byte(char *buf)
+{
+    return *(unsigned char*)buf;
+}
+
+uint16_t
+get_word(char *buf)
+{
+    return get_byte(buf) | (get_byte(buf+1) << 8);
+}
+
+uint32_t
+get_dword(char *buf)
+{
+    return get_byte(buf) |
+        (get_byte(buf+1) << 8) |
+        (get_byte(buf+2) << 16) |
+        (get_byte(buf+3) << 24);
+}
+
+void
+get_char(char *buf, char *p, size_t size)
+{
+    memcpy(p, buf, size);
+}
+
+void
+put_byte(char *buf, int c)
+{
+    *buf = (unsigned char)(c & 0xff);
+}
+
+void
+put_word(char *buf, uint16_t c)
+{
+    put_byte(buf,   c);
+    put_byte(buf+1, c>>8);
+}
+
+void
+put_dword(char *buf, uint32_t c)
+{
+    put_byte(buf, c);
+    put_byte(buf+1, c>>8);
+    put_byte(buf+2, c>>16);
+    put_byte(buf+3, c>>24);
+}
+
+void
+put_char(char *buf, char *p, size_t size)
+{
+    memcpy(buf, p, size);
+}
+
+#endif
+time_t
+ftime_to_time_t(uint32_t ftime)
+{
+    struct tm tm;
+    /* ftime is time structure on MS-DOS
+
+    32              24              16               8
+     0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+      |-------------|-------|---------|---------|-----------|---------|
+       year(-1980)   month   day       hour      minute      second/2
+       (1-127)       (1-12)  (1-31)    (0-23)    (0-59)      (0-29)
+    */
+
+    memset(&tm, 0, sizeof(tm));
+    tm.tm_year = (ftime >> 25) + 1980 - 1900;
+    tm.tm_mon  = ((ftime >> 21) & 0x0f) - 1;
+    tm.tm_mday = (ftime >> 16) & 0x1f;
+    tm.tm_hour = (ftime >> 11) & 0x1f;
+    tm.tm_min  = (ftime >>  5) & 0x3f;
+    tm.tm_sec  = (ftime & 0x1f) * 2;
+
+    return mktime(&tm);
+}
+
+uint32_t
+time_t_to_ftime(time_t t)
+{
+    struct tm tm;
+    /* ftime is time structure on MS-DOS
+
+    32              24              16               8
+     0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+      |-------------|-------|---------|---------|-----------|---------|
+       year(-1980)   month   day       hour      minute      second/2
+       (1-127)       (1-12)  (1-31)    (0-23)    (0-59)      (0-29)
+    */
+
+#if HAVE_LOCALTIME_R
+    localtime_r(&t, &tm);
+#else
+    tm = *localtime(&t);
+#endif
+
+    return (uint32_t)(tm.tm_year + 1900 - 1980) << 25
+        | (uint32_t)(tm.tm_mon + 1) << 21
+        | (uint32_t)(tm.tm_mday)    << 16
+        | (uint32_t)(tm.tm_hour)    << 11
+        | (uint32_t)(tm.tm_min)     <<  5
+        | (uint32_t)(tm.tm_sec / 2);
+}
+
+
+/*
+ * level 0 header
+ *
+ *
+ * offset  size  field name
+ * ----------------------------------
+ *     0      1  header size    [*1]
+ *     1      1  header sum
+ *            ---------------------------------------
+ *     2      5  method ID                         ^
+ *     7      4  packed size    [*2]               |
+ *    11      4  original size                     |
+ *    15      2  time                              |
+ *    17      2  date                              |
+ *    19      1  attribute                         | [*1] header size (X+Y+22)
+ *    20      1  level (0x00 fixed)                |
+ *    21      1  name length                       |
+ *    22      X  pathname                          |
+ * X +22      2  file crc (CRC-16)                 |
+ * X +24      Y  ext-header(old style)             v
+ * -------------------------------------------------
+ * X+Y+24        data                              ^
+ *                 :                               | [*2] packed size
+ *                 :                               v
+ * -------------------------------------------------
+ *
+ * ext-header(old style)
+ *     0      1  ext-type ('U')
+ *     1      1  minor version
+ *     2      4  UNIX time
+ *     6      2  mode
+ *     8      2  uid
+ *    10      2  gid
+ *
+ * attribute (MS-DOS)
+ *    bit1  read only
+ *    bit2  hidden
+ *    bit3  system
+ *    bit4  volume label
+ *    bit5  directory
+ *    bit6  archive bit (need to backup)
+ *
+ */
 static int
-read_header(void)
+read_header_lv0(FILE *fp, char *buf, struct lzh_header *h)
 {
-    headersize = (uchar) fgetc(arcfile);
-    if (headersize == 0)
-        return 0;               /* end of archive */
-    headersum = (uchar) fgetc(arcfile);
-    fread_crc(header, headersize, arcfile);     /* CRC not used */
-    if (calc_headersum() != headersum)
+    int headersize;
+    int headersum;
+    int ext_headersize;
+    int pos = 0;
+    int ext_size;
+
+    headersize = get_header(buf, &pos, 1);
+    headersum = get_header(buf, &pos, 1);
+
+    fread_crc(buf + 21, headersize - (21 - pos), fp);     /* CRC not used */
+
+    if (calc_headersum(buf+pos, headersize) != headersum)
         error("Header sum error");
-    compsize = get_from_header(5, 4);
-    origsize = get_from_header(9, 4);
-    file_crc = (uint) get_from_header(headersize - 5, 2);
-    filename[namelen] = '\0';
+
+    get_string(buf, &pos, 5, h->method);
+    h->compsize = get_header(buf, &pos, 4);
+    h->origsize = get_header(buf, &pos, 4);
+    h->mtime    = ftime_to_time_t(get_header(buf, &pos, 4));
+    /* attrib   = */ get_header(buf, &pos, 1);
+    h->level    = get_header(buf, &pos, 1); /* header level */
+    h->namelen  = get_header(buf, &pos, 1);
+    if (pos + h->namelen > headersize+2) {
+        warn("path name is too long");
+        h->namelen = headersize+2-pos;
+    }
+    get_string(buf, &pos, h->namelen, h->filename);
+    h->filename[h->namelen] = 0;
+    h->file_crc = get_header(buf, &pos, 2);
+    h->os_id    = 0;            /* generic */
+
+    ext_size = headersize - pos;
+
+    if (ext_size > 0) {
+        if (ext_size > 1) {
+            h->os_id = get_header(buf, &pos, 1);
+            ext_size--;
+        }
+        if (ext_size > 1) {
+            get_header(buf, &pos, 1); /* minor version */
+            ext_size--;
+        }
+        if (ext_size > 4) {
+            h->mtime = get_header(buf, &pos, 4);
+            ext_size -= 4;
+        }
+        if (ext_size > 2) {
+            get_header(buf, &pos, 2);         /* mode */
+            ext_size -= 2;
+        }
+        if (ext_size > 2) {
+            get_header(buf, &pos, 2);         /* uid */
+            ext_size -= 2;
+        }
+        if (ext_size > 2) {
+            get_header(buf, &pos, 2);         /* gid */
+            ext_size -= 2;
+        }
+    }
+
     return 1;                   /* success */
 }
 
-static void
-write_header(void)
+/*
+ * level 1 header
+ *
+ *
+ * offset   size  field name
+ * -----------------------------------
+ *     0       1  header size   [*1]
+ *     1       1  header sum
+ *             -------------------------------------
+ *     2       5  method ID                        ^
+ *     7       4  skip size     [*2]               |
+ *    11       4  original size                    |
+ *    15       2  time                             |
+ *    17       2  date                             |
+ *    19       1  attribute (0x20 fixed)           | [*1] header size (X+Y+25)
+ *    20       1  level (0x01 fixed)               |
+ *    21       1  name length                      |
+ *    22       X  filename                         |
+ * X+ 22       2  file crc (CRC-16)                |
+ * X+ 24       1  OS ID                            |
+ * X +25       Y  ???                              |
+ * X+Y+25      2  next-header size                 v
+ * -------------------------------------------------
+ * X+Y+27      Z  ext-header                       ^
+ *                 :                               |
+ * -----------------------------------             | [*2] skip size
+ * X+Y+Z+27       data                             |
+ *                 :                               v
+ * -------------------------------------------------
+ *
+ */
+static int
+read_header_lv1(FILE *fp, char *buf, struct lzh_header *h)
+{
+    int headersize;
+    int headersum;
+    int ext_headersize;
+    char dirname[1024] = "";
+    int dirnamelen = 0;
+    int pos = 0;
+
+    headersize = get_header(buf, &pos, 1);
+    headersum = get_header(buf, &pos, 1);
+
+    fread_crc(buf + 21, headersize - (21 - 2), fp);     /* CRC not used */
+
+    if (calc_headersum(&buf[pos], headersize) != headersum)
+        error("Header sum error");
+
+    get_string(buf, &pos, 5, h->method);
+    h->compsize = get_header(buf, &pos, 4);
+    h->origsize = get_header(buf, &pos, 4);
+    h->mtime    = ftime_to_time_t(get_header(buf, &pos, 4));
+    get_header(buf, &pos, 1);   /* attribute */
+    h->level = get_header(buf, &pos, 1); /* header level */
+    h->namelen = get_header(buf, &pos, 1);
+    get_string(buf, &pos, h->namelen, h->filename);
+    h->filename[h->namelen] = 0;
+    h->file_crc = get_header(buf, &pos, 2);
+    h->os_id = get_header(buf, &pos, 1);
+
+    ext_headersize = get_header(buf, &pos, 2);
+
+    while (ext_headersize != 0) {
+        char extbuf[4096];
+        uchar ext_type;
+        int extpos = 0;
+
+        h->compsize -= ext_headersize;
+
+        if (fread(extbuf, ext_headersize, 1, fp) != 1) {
+            error("can't read ext header");
+        }
+
+        ext_type = get_header(extbuf, &extpos, 1);
+        switch (ext_type) {
+        case 1:
+            /* filename header */
+            h->namelen = ext_headersize - 3;
+            get_string(extbuf, &extpos, h->namelen, h->filename);
+            h->filename[h->namelen] = 0;
+            break;
+        case 2:
+            /* dirname header */
+            dirnamelen = ext_headersize - 3;
+            get_string(extbuf, &extpos, dirnamelen, dirname);
+            dirname[dirnamelen] = 0;
+            break;
+        case 0x54:
+            h->mtime = get_header(extbuf, &extpos, 4);
+            break;
+        default:
+            break;
+        }
+        extpos = ext_headersize - 2;
+        ext_headersize = get_header(extbuf, &extpos, 2);
+    }
+
+    if (dirnamelen > 0 && dirname[dirnamelen-1] != '/') {
+        dirname[dirnamelen++] = '/';
+    }
+
+    strcat(dirname, h->filename);
+    h->namelen = strlen(dirname);
+    strcpy(h->filename, dirname);
+
+    return 1;                   /* success */
+}
+
+/*
+ * level 2 header
+ *
+ *
+ * offset   size  field name
+ * --------------------------------------------------
+ *     0       2  total header size [*1]           ^
+ *             -----------------------             |
+ *     2       5  method ID                        |
+ *     7       4  packed size       [*2]           |
+ *    11       4  original size                    |
+ *    15       4  time                             |
+ *    19       1  RESERVED (0x20 fixed)            | [*1] total header size
+ *    20       1  level (0x02 fixed)               |      (X+26+(1))
+ *    21       2  file crc (CRC-16)                |
+ *    23       1  OS ID                            |
+ *    24       2  next-header size                 |
+ * -----------------------------------             |
+ *    26       X  ext-header                       |
+ *                 :                               |
+ * -----------------------------------             |
+ * X +26      (1) padding                          v
+ * -------------------------------------------------
+ * X +26+(1)      data                             ^
+ *                 :                               | [*2] packed size
+ *                 :                               v
+ * -------------------------------------------------
+ *
+ */
+static int
+read_header_lv2(FILE *fp, char *buf, struct lzh_header *h)
 {
-    fputc(headersize, outfile);
-    /* We've destroyed file_crc by null-terminating filename. */
-    put_to_header(headersize - 5, 2, (ulong) file_crc);
-    fputc(calc_headersum(), outfile);
-    fwrite_crc(header, headersize, outfile);    /* CRC not used */
+    int headersize;
+    int headersum;
+    int ext_headersize;
+    int remainder;
+    char dirname[1024] = "";
+    int dirnamelen = 0;
+    int pos = 0;
+
+    headersize = get_header(buf, &pos, 2);
+
+    fread_crc(buf + 21, 26 - 21, fp);     /* CRC not used */
+
+    get_string(buf, &pos, 5, h->method);
+    h->compsize = get_header(buf, &pos, 4);
+    h->origsize = get_header(buf, &pos, 4);
+    h->mtime    = get_header(buf, &pos, 4);
+    get_header(buf, &pos, 1);         /* attrib */
+    h->level    = get_header(buf, &pos, 1); /* header level */
+    h->file_crc = get_header(buf, &pos, 2);
+    h->os_id    = get_header(buf, &pos, 1);
+
+    ext_headersize = get_header(buf, &pos, 2);
+
+    remainder = headersize - pos;
+
+    while (ext_headersize != 0) {
+        char extbuf[4096];
+        uchar ext_type;
+        int extpos = 0;
+
+        remainder -= ext_headersize;
+
+        if (fread(extbuf, ext_headersize, 1, fp) != 1) {
+            error("can't read ext header");
+        }
+        ext_type = get_header(extbuf, &extpos, 1);
+        switch (ext_type) {
+        case 0:
+            /* header crc */
+            break;
+        case 1:
+            /* filename header */
+            h->namelen = ext_headersize - 3;
+            get_string(extbuf, &extpos, h->namelen, h->filename);
+            h->filename[h->namelen] = 0;
+            break;
+        case 2:
+            /* dirname header */
+            dirnamelen = ext_headersize - 3;
+            get_string(extbuf, &extpos, dirnamelen, dirname);
+            dirname[dirnamelen] = 0;
+            break;
+        default:
+            break;
+        }
+        extpos = ext_headersize - 2;
+        ext_headersize = get_header(extbuf, &extpos, 2);
+    }
+
+    if (dirnamelen > 0 && dirname[dirnamelen-1] != '/') {
+        dirname[dirnamelen++] = '/';
+    }
+
+    strcat(dirname, h->filename);
+    h->namelen = strlen(dirname);
+    strcpy(h->filename, dirname);
+
+    while (remainder > 0) {
+        fgetc(fp); /* skip padding */
+        remainder--;
+    }
+
+    return 1;                   /* success */
+}
+
+static int
+read_header(FILE *fp, struct lzh_header *h)
+{
+    char buf[4096];
+    int ret;
+
+    ret = fgetc(fp);
+    buf[0] = (uchar)ret;
+    if (buf[0] == 0 || ret == EOF)
+        return 0;               /* end of archive */
+    fread_crc(buf + 1, 21 - 1, fp);
+    switch (buf[20]) {
+    case 0:
+        return read_header_lv0(fp, buf, h);
+        break;
+    case 1:
+        return read_header_lv1(fp, buf, h);
+        break;
+    case 2:
+        return read_header_lv2(fp, buf, h);
+        break;
+    default:
+        error("unknown level (%d)\n", buf[20]);
+        break;
+    }
+
+    return 1;                   /* success */
+}
+
+
+void
+write_header_lv0(FILE *fp, struct lzh_header *h)
+{
+    char buf[4096];
+    int sum;
+    int headersize;
+    int pos = 0;
+
+    headersize = 22;
+    if (!opts.generic)
+        headersize += 12;       /* extended header size */
+
+    if (headersize + h->namelen > 255) {
+        warn("path name is too long");
+        h->namelen = 255 - headersize;
+        headersize = 255;
+    }
+    else {
+        headersize += h->namelen;
+    }
+
+    put_header(buf, &pos, 1, headersize);
+    put_header(buf, &pos, 1, 0); /* dummy */
+
+    put_string(buf, &pos, 5, h->method);
+    put_header(buf, &pos, 4, h->compsize); /* packed size */
+    put_header(buf, &pos, 4, h->origsize); /* original size */
+    put_header(buf, &pos, 4, time_t_to_ftime(h->mtime)); /* ftime */
+    put_header(buf, &pos, 1, 0x20);     /* attribute */
+    put_header(buf, &pos, 1, 0);        /* level */
+    put_header(buf, &pos, 1, h->namelen); /* length of pathname */
+    put_string(buf, &pos, h->namelen, h->filename);
+    put_header(buf, &pos, 2, h->file_crc);
+
+    if (!opts.generic) {
+        /* extended header for Unix */
+        put_header(buf, &pos, 1, 'U');  /* OS type */
+        put_header(buf, &pos, 1, '\0'); /* minor version */
+        put_header(buf, &pos, 4, h->mtime); /* time_t */
+        put_header(buf, &pos, 2, 0100000);  /* mode */
+        put_header(buf, &pos, 2, 0);  /* uid */
+        put_header(buf, &pos, 2, 0);  /* gid */
+    }
+
+    sum = calc_headersum(buf+2, headersize);
+    put_header_tmp(buf, 1, 1, sum);
+
+    fwrite_crc(buf, headersize+2, fp);
+}
+
+void
+write_header_lv1(FILE *fp, struct lzh_header *h)
+{
+    char buf[4096];
+    int sum;
+    int headersize;
+    int extsize = 0;
+    int pos = 0;
+    int extpos;
+    char *dirname, *fname;
+    int dirnamelen;
+
+    fname  = basename(h->filename);
+    dirname   = h->filename;
+    dirnamelen = fname - dirname;
+    h->namelen = strlen(fname);
+    printf("namelen = %d\n", h->namelen);
+
+    headersize = 25;
+
+    put_header(buf, &pos, 1, 0); /* dummy */
+    put_header(buf, &pos, 1, 0); /* dummy */
+    put_string(buf, &pos, 5, h->method);
+    put_header(buf, &pos, 4, h->compsize); /* packed size */
+    put_header(buf, &pos, 4, h->origsize); /* original size */
+    put_header(buf, &pos, 4, time_t_to_ftime(h->mtime)); /* ftime */
+    put_header(buf, &pos, 1, 0x20);     /* attribute */
+    put_header(buf, &pos, 1, 1);        /* level */
+    if (headersize + h->namelen > 255)
+        put_header(buf, &pos, 1, 0);            /* length of pathname */
+    else {
+        put_header(buf, &pos, 1, h->namelen);   /* length of pathname */
+        put_string(buf, &pos, h->namelen, fname);
+        headersize += h->namelen;
+    }
+    put_header_tmp(buf, 0, 1, headersize); /* header size */
+    put_header(buf, &pos, 2, h->file_crc);
+    if (opts.generic)
+        put_header(buf, &pos, 1, '\0');
+    else
+        put_header(buf, &pos, 1, 'U');
+
+    extpos = pos;
+    put_header(buf, &pos, 2, 7); /* next header size */
+    put_header(buf, &pos, 1, 0x54); /* time stamp */
+    put_header(buf, &pos, 4, h->mtime); /* time_t */
+
+    if (h->namelen > 0) {
+        put_header(buf, &pos, 2, 3 + h->namelen);
+        put_header(buf, &pos, 1, 1); /* 0x01: filename header */
+        put_string(buf, &pos, h->namelen, fname); /* filename */
+    }
+
+    if (dirnamelen > 0) {
+        put_header(buf, &pos, 2, 3 + dirnamelen);
+        put_header(buf, &pos, 1, 2); /* 0x02: dirname header */
+        put_string(buf, &pos, dirnamelen, dirname); /* dirname */
+    }
+
+    extsize = pos - extpos;
+    put_header(buf, &pos, 2, 0); /* next header size (end of header) */
+
+    put_header_tmp(buf, 7, 4, h->compsize+extsize);    /* packed size */
+
+    sum = calc_headersum(buf+2, headersize);
+    put_header_tmp(buf, 1, 1, sum);
+
+    fwrite_crc(buf, headersize+2+extsize, fp);
+}
+
+void
+write_header_lv2(FILE *fp, struct lzh_header *h)
+{
+    char buf[4096], *crcptr;
+    int headersize;
+    extern ushort crctable[];
+    char dirname[1024] = "", *fname;
+    int dirnamelen, len;
+    int pos = 0;
+
+    put_header(buf, &pos, 2, 0); /* dummy */
+    put_string(buf, &pos, 5, h->method);
+    put_header(buf, &pos, 4, h->compsize); /* packed size */
+    put_header(buf, &pos, 4, h->origsize); /* original size */
+    put_header(buf, &pos, 4, h->mtime);    /* time_t */
+    put_header(buf, &pos, 1, 0x20);        /* DOS attribute (0x20 fixed) */
+    put_header(buf, &pos, 1, 2);           /* level */
+    put_header(buf, &pos, 2, h->file_crc);
+    if (opts.generic)
+        put_header(buf, &pos, 1, '\0');
+    else
+        put_header(buf, &pos, 1, 'U');
+
+    put_header(buf, &pos, 2, 5);
+    put_header(buf, &pos, 1, 0); /* 0x00: header crc */
+    crcptr = &buf[pos];
+    put_header(buf, &pos, 2, 0); /* crc (dummy) */
+
+    fname = basename(h->filename);
+    len = strlen(fname);
+
+    put_header(buf, &pos, 2, 3 + len);
+    put_header(buf, &pos, 1, 1); /* 0x01: filename header */
+    put_string(buf, &pos, len, fname); /* filename */
+
+    {
+        char *ptr;
+
+        ptr = strrchr(h->filename, '/');
+        if (ptr) {
+            /* 0123 */
+            /* abc/ */
+            /* 3 - 0 = 3 */
+            dirnamelen = ptr - h->filename;
+            strncpy(dirname, h->filename, dirnamelen);
+            dirname[dirnamelen+ 1] = 0;
+        }
+    }
+
+    if (*dirname) {
+        put_header(buf, &pos, 2, 3 + dirnamelen);
+        put_header(buf, &pos, 1, 2); /* 0x02: dirname header */
+        put_string(buf, &pos, dirnamelen, dirname); /* dirname */
+    }
+
+    put_header(buf, &pos, 2, 0); /* next header size (end of header) */
+
+    /* padding */
+    if (pos % 256 == 0) {
+        put_header(buf, &pos, 1, 0);
+    }
+    headersize = pos;
+
+    put_header_tmp(buf, 0, 2, headersize);
+
+    {
+        int i;
+
+        crc = INIT_CRC;
+        for (i = 0; i < headersize; i++)
+            UPDATE_CRC(buf[i]);
+        put_header_tmp(crcptr, 0, 2, crc);
+    }
+
+    fwrite_crc(buf, headersize, fp);
+}
+
+void
+write_header(FILE *fp, struct lzh_header *h)
+{
+    switch (h->level) {
+    case 0:
+        write_header_lv0(fp, h);
+        break;
+    case 1:
+        write_header_lv1(fp, h);
+        break;
+    case 2:
+        write_header_lv2(fp, h);
+        break;
+    default:
+        error("unknown level (%d)", h->level);
+        break;
+    }
 }
 
 static void
-skip(void)
+skip(FILE *fp, struct lzh_header *h)
 {
-    fseek(arcfile, compsize, SEEK_CUR);
+    int i;
+    if (opts.archive_to_stdio)
+        for (i = 0; i < h->compsize; i++)
+            fgetc(fp);
+    else
+        fseek(fp, h->compsize, SEEK_CUR);
 }
 
 static void
-copy(void)
+copy(FILE *arcfile, FILE *outfile, struct lzh_header *h)
 {
     uint n;
+    uchar buffer[MAXDICSIZ];
 
-    write_header();
-    while (compsize != 0) {
-        n = (uint) ((compsize > DICSIZ) ? DICSIZ : compsize);
+    write_header(outfile, h);
+    while (h->compsize != 0) {
+        n = (uint) ((h->compsize > sizeof(buffer)) ? sizeof(buffer) : h->compsize);
         if (fread((char *) buffer, 1, n, arcfile) != n)
             error("Can't read");
         if (fwrite((char *) buffer, 1, n, outfile) != n)
             error("Can't write");
-        compsize -= n;
+        h->compsize -= n;
     }
 }
 
@@ -164,10 +929,11 @@ static void
 store(void)
 {
     uint n;
+    uchar buffer[MAXDICSIZ];
 
     origsize = 0;
     crc = INIT_CRC;
-    while ((n = fread((char *) buffer, 1, DICSIZ, infile)) != 0) {
+    while ((n = fread((char *) buffer, 1, sizeof(buffer), infile)) != 0) {
         fwrite_crc(buffer, n, outfile);
         origsize += n;
     }
@@ -175,51 +941,126 @@ store(void)
 }
 
 static int
-add(int replace_flag)
+add_dir(int replace_flag, struct lzh_header *h)
 {
     long headerpos, arcpos;
     uint r;
 
-    if ((infile = fopen(filename, "rb")) == NULL) {
-        fprintf(stderr, "Can't open %s\n", filename);
+    h->origsize = h->compsize = 0;
+    h->file_crc = INIT_CRC;
+
+    headerpos = ftell(outfile);
+    write_header(outfile, h);
+    arcpos = ftell(outfile);
+
+    if (opts.quiet < 2)
+        printf(" %d.%d%%\n", r / 10, r % 10);
+    return 1;                   /* success */
+}
+
+static int
+add_1(int replace_flag, struct lzh_header *h)
+{
+    long headerpos, arcpos;
+    uint r;
+
+    if ((infile = fopen(h->filename, "rb")) == NULL) {
+        fprintf(stderr, "Can't open %s\n", h->filename);
         return 0;               /* failure */
     }
     if (replace_flag) {
-        printf("Replacing %s ", filename);
-        skip();
+        if (opts.quiet < 2)
+            printf("Replacing %s ", h->filename);
     }
-    else
-        printf("Adding %s ", filename);
+    else {
+        if (opts.quiet < 2)
+            printf("Adding %s ", h->filename);
+    }
+
     headerpos = ftell(outfile);
-    namelen = strlen(filename);
-    headersize = 25 + namelen;
-    memcpy(header, "-lh5-", 5); /* compress */
-    write_header();             /* temporarily */
+    write_header(outfile, h);
     arcpos = ftell(outfile);
+
     origsize = compsize = 0;
-    unpackable = 0;
     crc = INIT_CRC;
-    encode();
+    if (opts.nocompress) {
+        unpackable = 1;
+    }
+    else {
+        unpackable = 0;
+        encode();
+    }
+
     if (unpackable) {
-        header[3] = '0';        /* store */
+        memcpy(h->method, "-lh0-", sizeof(h->method));  /* store */
         rewind(infile);
         fseek(outfile, arcpos, SEEK_SET);
         store();
     }
-    file_crc = crc ^ INIT_CRC;
+    h->file_crc = crc ^ INIT_CRC;
     fclose(infile);
-    put_to_header(5, 4, compsize);
-    put_to_header(9, 4, origsize);
-    memcpy(header + 13, "\0\0\0\0\x20\x01", 6);
-    memcpy(header + headersize - 3, "\x20\0\0", 3);
+
+    h->compsize = compsize;
+    h->origsize = origsize;
+
     fseek(outfile, headerpos, SEEK_SET);
-    write_header();             /* true header */
+    write_header(outfile, h);
     fseek(outfile, 0L, SEEK_END);
     r = ratio(compsize, origsize);
-    printf(" %d.%d%%\n", r / 10, r % 10);
+    if (opts.quiet < 2)
+        printf(" %d.%d%%\n", r / 10, r % 10);
     return 1;                   /* success */
 }
 
+static int
+add(int replace_flag, char *filename, int namelen)
+{
+    struct lzh_header h;
+    struct stat st;
+
+    memset(&h, 0, sizeof(h));
+
+    h.level = opts.header_level;
+
+    strcpy(h.filename, filename);
+    h.namelen = namelen;
+
+    stat(h.filename, &st);
+
+    h.mtime = st.st_mtime;
+    if (S_ISDIR(st.st_mode)) {
+        DIR *dir;
+        struct dirent *ent;
+
+        memcpy(h.method, "-lhd-", sizeof(h.method));  /* directory */
+        add_dir(replace_flag, &h);
+
+        dir = opendir(h.filename);
+        if (dir == NULL)
+            error("cannot open directory: \"%s\"", h.filename);
+
+        while ((ent = readdir(dir)) != 0) {
+            char filename[1024];
+
+            if (string_equal(ent->d_name, ".") ||
+                string_equal(ent->d_name, ".."))
+                continue;
+
+            h.namelen = path_addsep(h.filename, sizeof(h.filename));
+
+            string_cat(filename, sizeof(filename),
+                       h.filename, ent->d_name, NULL);
+
+            add(replace_flag, filename, strlen(filename));
+        }
+        closedir(dir);
+    }
+    else {
+        memcpy(h.method, opts.method->id, sizeof(h.method));  /* compress */
+        add_1(replace_flag, &h);
+    }
+}
+
 int
 get_line(char *s, int n)
 {
@@ -234,86 +1075,122 @@ get_line(char *s, int n)
 }
 
 static void
-extract(int to_file)
+extract(int to_file, struct lzh_header *h)
 {
-    int n, method;
-    uint ext_headersize;
+    int n;
+    uchar buffer[MAXDICSIZ];
+
+    outfile = NULL;
 
     if (to_file) {
-        while ((outfile = fopen(filename, "wb")) == NULL) {
-            fprintf(stderr, "Can't open %s\nNew filename: ", filename);
-            if (get_line(filename, FNAME_MAX) == 0) {
-                fprintf(stderr, "Not extracted\n");
-                skip();
-                return;
+        if (memcmp(h->method, "-lhd-", sizeof(h->method)) == 0) {
+            /* directory */
+            if (mkdir(h->filename, 0777) == -1) {
+                if (errno != EEXIST)
+                    error("cannot make directory \"%s\"", opts.outdir);
             }
-            namelen = strlen(filename);
         }
-        printf("Extracting %s ", filename);
+        else {
+            /* regular file */
+            if (file_exists(h->filename)) {
+                if (!opts.force_extract) {
+                    message("'%s' has been already exist. skip", h->filename);
+                    skip(arcfile, h);
+                    return;
+                }
+            }
+            while ((outfile = fopen(h->filename, "wb")) == NULL) {
+                fprintf(stderr, "Can't open %s\nNew filename: ", h->filename);
+                if (get_line(h->filename, FNAME_MAX) == 0) {
+                    fprintf(stderr, "Not extracted\n");
+                    skip(arcfile, h);
+                    return;
+                }
+                h->namelen = strlen(h->filename);
+            }
+        }
+        if (opts.quiet < 2)
+            printf("Extracting %s ", h->filename);
     }
     else {
         outfile = stdout;
-        printf("===== %s =====\n", filename);
+        if (opts.quiet < 2)
+            printf("===== %s =====\n", h->filename);
     }
     crc = INIT_CRC;
-    method = header[3];
-    header[3] = ' ';
-    if (!strchr("045", method) || memcmp("-lh -", header, 5)) {
-        fprintf(stderr, "Unknown method: %u\n", method);
-        skip();
+    opts.method = which_method(h->method);
+    if (opts.method == NULL) {
+        fprintf(stderr, "Unknown method: %.5s\n", h->method);
+        skip(arcfile, h);
     }
     else {
-        ext_headersize = (uint) get_from_header(headersize - 2, 2);
-        while (ext_headersize != 0) {
-            fprintf(stderr, "There's an extended header of size %u.\n",
-                    ext_headersize);
-            compsize -= ext_headersize;
-            if (fseek(arcfile, ext_headersize - 2, SEEK_CUR))
-                error("Can't read");
-            ext_headersize = fgetc(arcfile);
-            ext_headersize += (uint) fgetc(arcfile) << 8;
-        }
         crc = INIT_CRC;
-        if (method != '0')
+        if (opts.method->dicbit != 0)
             decode_start();
-        while (origsize != 0) {
-            n = (uint) ((origsize > DICSIZ) ? DICSIZ : origsize);
-            if (method != '0')
+        while (h->origsize != 0) {
+            n = (uint) ((h->origsize > MAXDICSIZ) ? MAXDICSIZ : h->origsize);
+            if (opts.method->dicbit != 0)
                 decode(n, buffer);
             else if (fread((char *) buffer, 1, n, arcfile) != n)
                 error("Can't read");
             fwrite_crc(buffer, n, outfile);
-            if (outfile != stdout)
-                putc('.', stderr);
-            origsize -= n;
+            if (outfile != stdout && opts.quiet < 1) {
+                putc('.', stdout);
+            }
+            h->origsize -= n;
         }
     }
-    if (to_file)
-        fclose(outfile);
-    else
-        outfile = NULL;
-    printf("\n");
-    if ((crc ^ INIT_CRC) != file_crc)
-        fprintf(stderr, "CRC error\n");
+
+    if ((crc ^ INIT_CRC) != h->file_crc)
+        error("CRC error");
+
+    if (to_file) {
+        fprintf(stdout, "\n");
+        if (outfile) {
+            struct utimbuf ut;
+
+            fclose(outfile);
+
+            ut.actime = ut.modtime = h->mtime;
+            utime(h->filename, &ut);
+        }
+    }
+    outfile = NULL;
 }
 
 static void
 list_start(void)
 {
-    printf("Filename         Original Compressed Ratio CRC Method\n");
+    if (opts.quiet < 2)
+        printf("%-14.14s %-7.7s %10.10s %10.10s %-5.5s %-4.4s %-5.5s %-4.4s\n",
+               "Filename",
+               "OS",
+               "Original",
+               "Compressed",
+               "Ratio",
+               "CRC",
+               "Method",
+               "Lv");
 }
 
 static void
-list(void)
+list(struct lzh_header *h)
 {
     uint r;
 
-    printf("%-14s", filename);
-    if (namelen > 14)
+    printf("%-14.*s", h->namelen, h->filename);
+    if (h->namelen > 14)
         printf("\n              ");
-    r = ratio(compsize, origsize);
-    printf(" %10lu %10lu %u.%03u %04X %5.5s\n",
-           origsize, compsize, r / 1000, r % 1000, file_crc, header);
+    r = ratio(h->compsize, h->origsize);
+    printf(" %-7s %10lu %10lu %u.%03u %04x %-6.6s [%d]\n",
+           os_string(h->os_id),
+           h->origsize,
+           h->compsize,
+           r / 1000,
+           r % 1000,
+           h->file_crc,
+           h->method,
+           h->level);
 }
 
 static int
@@ -339,138 +1216,367 @@ match(char *s1, char *s2)
 }
 
 static int
-search(int argc, char *argv[])
+search(int argc, char *argv[], struct lzh_header *h)
 {
     int i;
 
-    if (argc == 3)
-        return 1;
-    for (i = 3; i < argc; i++)
-        if (match(filename, argv[i]))
-            return 1;
+    if (argc == 0)
+        return -1;
+    for (i = 0; i < argc; i++)
+        if (argv[i] && match(h->filename, argv[i]))
+            return i+1;
     return 0;
 }
 
 static void
 exitfunc(void)
 {
-    fclose(outfile);
     remove(temp_name);
 }
 
+#include "getopt_long.h"
+
+int
+parse_args(int argc, char **argv)
+{
+    int c;
+
+    for (;;) {
+        /* int this_option_optind = optind ? optind : 1; */
+        int option_index = 0;
+
+        enum {
+            LHA_OPT_HELP = 128,
+            LHA_OPT_VERSION,
+        };
+
+        static struct option long_options[] = {
+            /* name, has_arg, *flag, val */
+            /* has_arg:
+               no_argument (0)
+               required_argument (1)
+               optional_argument (2)
+               flag:
+               NULL: getopt_long() return val
+               non-NULL: getopt_long() return 0, and *flag set val.
+            */
+            {"help", no_argument, NULL, LHA_OPT_HELP},
+            {"version", no_argument, NULL, LHA_OPT_VERSION},
+            {0, 0, 0, 0}
+        };
+
+        c = getopt_long(argc, argv, "012fgo[567]q[012]vw:z",
+                        long_options, &option_index);
+
+        if (c == -1) break;     /* end of parsing options */
+
+        switch (c) {
+        case '?':
+            print_usage();
+            break;
+        case 0:
+            /* set value by long option */
+            break;
+        case '0': case '1': case '2':
+            /* header level */
+            opts.header_level = c - '0';
+            break;
+        case 'f':
+            opts.force_extract = 1;
+            break;
+        case 'g':
+            opts.generic = 1;
+            opts.header_level = 0;
+            break;
+        case 'o':
+            /* compress method */
+            {
+                int idx = 1;    /* -o means -lh1- method */
+
+                if (optarg)
+                    idx = *optarg - '0'; /* -lh[567]- method */
+
+                opts.method   = &methods[idx];
+            }
+            break;
+        case 'q':
+            /* quiet mode */
+            opts.quiet = 2;     /* -q is equivalent to -q2 */
+            if (optarg)
+                opts.quiet = *optarg - '0';
+            break;
+        case 'v':
+            /* verbose mode */
+            opts.verbose = 1;
+            break;
+
+        case 'w':
+            /* extract directory */
+            if (!optarg)
+                error("extract directory does not specified for `-w'");
+            if (*optarg == '=')
+                optarg++;
+
+            opts.outdir = optarg;
+            break;
+        case 'z':               /* no compress */
+            opts.nocompress = 1;
+            break;
+        case LHA_OPT_HELP:
+            print_usage();
+            break;
+        case LHA_OPT_VERSION:
+            print_version();
+            break;
+        default:
+            break;
+        }
+    }
+}
+
+FILE *
+open_tempfile()
+{
+    temp_name = tmpnam(NULL);
+    outfile = fopen(temp_name, "wb");
+    if (outfile == NULL)
+        error("Can't open temporary file");
+    atexit(exitfunc);
+
+    return outfile;
+}
+
 int
 main(int argc, char *argv[])
 {
-    int i, j, cmd, count, nfiles, found, done;
+    int i, cmd, count, nfiles, found, done;
+    char *archive_file;
+    struct lzh_header h;
+    int arc_count;
 
-    /* Check command line arguments. */
-    if (argc < 3
-        || argv[1][1] != '\0' || !strchr("AXRDPL", cmd = toupper(argv[1][0]))
-        || (argc == 3 && strchr("AD", cmd)))
-        error(usage);
+    INITIALIZE_OPTS(opts);
 
-    /* Wildcards used? */
-    for (i = 3; i < argc; i++)
-        if (strpbrk(argv[i], "*?"))
-            break;
-    if (cmd == 'A' && i < argc)
-        error("Filenames may not contain '*' and '?'");
-    if (i < argc)
-        nfiles = -1;            /* contains wildcards */
-    else
-        nfiles = argc - 3;      /* number of files to process */
+    if (argv[1] == 0)
+        print_usage();
+
+    /*take a command character */
+    {
+        char *arg1;
 
-    /* Open archive. */
-    arcfile = fopen(argv[2], "rb");
-    if (arcfile == NULL && cmd != 'A')
-        error("Can't open archive '%s'", argv[2]);
+        arg1 = argv[1];
+        if (arg1[0] == '-')
+            arg1++;
+        if (arg1[0] == 0)
+            print_usage();
 
-    /* Open temporary file. */
-    if (strchr("ARD", cmd)) {
-        temp_name = tmpnam(NULL);
-        outfile = fopen(temp_name, "wb");
-        if (outfile == NULL)
-            error("Can't open temporary file");
-        atexit(exitfunc);
+        cmd = *arg1;
+        if (arg1[1] == 0) {
+            /* -<cmd> -<opts> ... */
+            argv++;
+            argc--;
+        }
+        else {
+            /* -<cmd><opts> => -<opts> */
+            *arg1 = '-';
+        }
     }
-    else
-        temp_name = NULL;
+
+    parse_args(argc, argv);
+    argv += optind;
+    argc -= optind;
+
+    archive_file = argv[0];
+
+    if (strcmp(archive_file, "-") == 0)
+        opts.archive_to_stdio = 1;
+
+    argv++;
+    argc--;
+
+    temp_name = NULL;
 
     make_crctable();
-    count = done = 0;
+    count = done = nfiles = 0;
 
-    if (cmd == 'A') {
-        for (i = 3; i < argc; i++) {
-            for (j = 3; j < i; j++)
-                if (strcmp(argv[j], argv[i]) == 0)
-                    break;
-            if (j == i) {
-                strcpy(filename, argv[i]);
-                if (add(0))
-                    count++;
-                else
-                    argv[i][0] = 0;
+    switch (cmd) {
+    case 'a':
+    case 'u':
+    case 'c':
+        if (opts.archive_to_stdio)
+            opts.quiet = 2;
+
+        outfile = open_tempfile();
+        if (*argv == 0)
+            error("archived files are not specified.");
+
+        if (!opts.archive_to_stdio && (cmd == 'a' || cmd == 'u')) {
+            if (file_exists(archive_file)) {
+                arcfile = fopen(archive_file, "rb");
+                if (arcfile == NULL)
+                    error("Can't open archive '%s'", archive_file);
+
+                break;
             }
-            else
-                nfiles--;
         }
-        if (count == 0 || arcfile == NULL)
-            done = 1;
+        for (i = 0; i < argc; i++) {
+            add(0, argv[i], strlen(argv[i]));
+        }
+
+        fputc(0, outfile);      /* end of archive */
+        if (ferror(outfile))
+            error("Can't write");
+        fclose(outfile);
+        if (opts.archive_to_stdio) {
+            if (move_file_to_stream(temp_name, stdout) == -1)
+                error("fail to move_file_to_stream(): %s -> %s",temp_name,"stdout");
+        }
+        else {
+            unlink(archive_file);
+            if (xrename(temp_name, archive_file) == -1)
+                error("fail to rename(): %s -> %s",temp_name,archive_file);
+        }
+        exit(0);
+        break;
+    case 'r':
+    case 'd':
+        if (argc == 0) {
+            message("No files given in argument, do nothing.");
+            exit(0);
+        }
+        outfile = open_tempfile();
+    case 'x':
+    case 'p':
+    case 'l':
+    case 'v':
+        /* Open archive. */
+        if (opts.archive_to_stdio) {
+            arcfile = stdin;
+        }
+        else {
+            arcfile = fopen(archive_file, "rb");
+        }
+        if (arcfile == NULL)
+            error("Can't open archive '%s'", archive_file);
+
+        break;
+    default:
+        print_usage();
+        break;
+    }
+
+    /* change directory to extract dir */
+    if (cmd == 'x') {
+        if (opts.outdir) {
+            if (mkdir(opts.outdir, 0777) == -1) {
+                if (errno != EEXIST)
+                    error("cannot make directory \"%s\"", opts.outdir);
+            }
+
+            if (chdir(opts.outdir) == -1)
+                error("cannot change directory \"%s\"", opts.outdir);
+        }
     }
 
-    while (!done && read_header()) {
-        found = search(argc, argv);
+    arc_count = 0;
+
+    while (!done && read_header(arcfile, &h)) {
+
+        arc_count++;
+
+        compsize = h.compsize;
+        origsize = h.origsize;
+
+        found = search(argc, argv, &h);
         switch (cmd) {
-        case 'R':
-            if (found) {
-                if (add(1))
+        case 'a':
+        case 'u':
+            if (found>0) {
+                argv[found-1] = 0;
+
+                if (cmd == 'u' && h.mtime > file_mtime(h.filename)) {
+                    copy(arcfile, outfile, &h);
+                    break;
+                }
+
+                if (add(1, h.filename, h.namelen)) {
+                    skip(arcfile, &h);
                     count++;
+                }
                 else
-                    copy();
+                    copy(arcfile, outfile, &h);
             }
             else
-                copy();
+                copy(arcfile, outfile, &h);
             break;
-        case 'A':
-        case 'D':
+        case 'd':
             if (found) {
-                count += (cmd == 'D');
-                skip();
+                count++;
+                message("'%s' deleted", h.filename);
+                skip(arcfile, &h);
             }
             else
-                copy();
+                copy(arcfile, outfile, &h);
             break;
-        case 'X':
-        case 'P':
-            if (found) {
-                extract(cmd == 'X');
+        case 'x':
+        case 'p':
+            if (found != 0) {
+                extract(cmd == 'x', &h);
                 if (++count == nfiles)
                     done = 1;
             }
             else
-                skip();
+                skip(arcfile, &h);
             break;
-        case 'L':
-            if (found) {
+        case 'l':
+        case 'v':
+            if (found != 0) {
                 if (count == 0)
                     list_start();
-                list();
+                list(&h);
                 if (++count == nfiles)
                     done = 1;
             }
-            skip();
+            skip(arcfile, &h);
             break;
         }
     }
 
-    if (temp_name != NULL && count != 0) {
+    if (cmd == 'a' || cmd == 'u') {
+        for (i = 0; i < argc; i++) {
+            if (argv[i]) {
+                count++;
+                add(0, argv[i], strlen(argv[i]));
+            }
+        }
+    }
+
+    if (cmd != 'p') {
+        if (opts.quiet < 2)
+            printf("  %d files\n", count);
+    }
+
+    if (count > 0 && (cmd == 'd' || cmd == 'a' || cmd == 'u')) {
         fputc(0, outfile);      /* end of archive */
-        if (ferror(outfile) || fclose(outfile) == EOF)
+        if (ferror(outfile))
             error("Can't write");
-        remove(argv[2]);
-        rename(temp_name, argv[2]);
+        if (!opts.archive_to_stdio)
+            unlink(archive_file);
+        fclose(outfile);
+        fclose(arcfile);
+        if (cmd == 'd') {
+            if (arc_count > count) {
+                if (xrename(temp_name, archive_file) == -1)
+                    error("fail to rename(): %s -> %s",temp_name,archive_file);
+            }
+            else {
+                message("The archive file \"%s\" was removed because it would be empty.", archive_file);
+            }
+        }
+        else {
+            if (xrename(temp_name, archive_file) == -1)
+                error("fail to rename(): %s -> %s",temp_name,archive_file);
+        }
+        exit(0);
     }
-
-    printf("  %d files\n", count);
     return EXIT_SUCCESS;
 }