OSDN Git Service

Should not use the basename() function.
[lha/olha.git] / ar.c
diff --git a/ar.c b/ar.c
index 35319bc..585e43d 100644 (file)
--- a/ar.c
+++ b/ar.c
@@ -50,6 +50,8 @@ Structure of archive block (low order byte first):
 #include <errno.h>
 #include <dirent.h>
 #include <sys/stat.h>
+#include <time.h>
+#include <utime.h>
 #include "ar.h"
 
 extern char *basename(const char *);
@@ -162,25 +164,48 @@ os_string(char id)
 }
 
 static void
-put_to_header(char *buf, int i, int n, ulong x)
+put_header(char *buf, int *i, int n, uint32_t x)
 {
     while (--n >= 0) {
-        buf[i++] = (uchar) ((uint) x & 0xFF);
+        buf[(*i)++] = (uchar) (x & 0xFF);
         x >>= 8;
     }
 }
 
-static ulong
-get_from_header(char *buf, 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)
 {
-    ulong s;
+    memcpy(buf + *n, p, size);
+    *n += size;
+}
+
+static uint32_t
+get_header(char *buf, int *i, int size)
+{
+    uint32_t s;
+    int n;
 
     s = 0;
-    while (--n >= 0)
-        s = (s << 8) + buf[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(char *buf, int size)
 {
@@ -193,6 +218,7 @@ calc_headersum(char *buf, int size)
     return s & 0xFF;
 }
 
+#if 0
 int
 get_byte(char *buf)
 {
@@ -248,120 +274,344 @@ 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_lv0(FILE *fp, char *buf, struct lzh_header *h)
 {
     int headersize;
     int headersum;
     int ext_headersize;
+    int pos = 0;
+    int ext_size;
 
-    headersize = get_byte(&buf[0]);
-    headersum = get_byte(&buf[1]);
-
-    fread_crc(buf + 21, headersize - (21 - 2), fp);     /* CRC not used */
+    headersize = get_header(buf, &pos, 1);
+    headersum = get_header(buf, &pos, 1);
 
-    buf += 2;
+    fread_crc(buf + 21, headersize - (21 - pos), fp);     /* CRC not used */
 
-    if (calc_headersum(buf, headersize) != headersum)
+    if (calc_headersum(buf+pos, headersize) != headersum)
         error("Header sum error");
 
-    get_char(&buf[0], h->method, 5);
-    h->compsize = get_dword(&buf[5]);
-    h->origsize = get_dword(&buf[9]);
-    h->ftime    = get_dword(&buf[13]);
-    /* attrib   = get_byte(&buf[17]); */
-    h->level    = get_byte(&buf[18]); /* header level */
-    h->namelen = get_byte(&buf[19]);
-    get_char(&buf[20], h->filename, h->namelen);
+    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_word(&buf[20+h->namelen]);
+    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 */
 }
 
+/*
+ * 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_byte(&buf[0]);
-    headersum = get_byte(&buf[1]);
+    headersize = get_header(buf, &pos, 1);
+    headersum = get_header(buf, &pos, 1);
 
     fread_crc(buf + 21, headersize - (21 - 2), fp);     /* CRC not used */
 
-    buf += 2;
-
-    if (calc_headersum(buf, headersize) != headersum)
+    if (calc_headersum(&buf[pos], headersize) != headersum)
         error("Header sum error");
 
-    get_char(&buf[0], h->method, 5);
-    h->compsize = get_dword(&buf[5]);
-    h->origsize = get_dword(&buf[9]);
-    h->ftime    = get_dword(&buf[13]);
-    /* attrib   = get_byte(&buf[17]); */
-    h->level    = get_byte(&buf[18]); /* header level */
-    h->namelen = get_byte(&buf[19]);
-    get_char(&buf[20], h->filename, h->namelen);
+    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_word(&buf[20+h->namelen]);
-    h->os_id    = get_byte(&buf[20+h->namelen+2]);
+    h->file_crc = get_header(buf, &pos, 2);
+    h->os_id = get_header(buf, &pos, 1);
 
-    ext_headersize = get_word(&buf[20+h->namelen+3]);
+    ext_headersize = get_header(buf, &pos, 2);
 
     while (ext_headersize != 0) {
-        fprintf(stderr, "There's an extended header of size %u.\n",
-                ext_headersize);
+        char extbuf[4096];
+        uchar ext_type;
+        int extpos = 0;
+
         h->compsize -= ext_headersize;
 
-        /* skip ext header */
-        if (fseek(arcfile, ext_headersize - 2, SEEK_CUR))
-            error("Can't read");
-        ext_headersize = fgetc(arcfile);
-        ext_headersize += (uint) fgetc(arcfile) << 8;
+        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)
 {
     int headersize;
     int headersum;
     int ext_headersize;
-    char extbuf[1024];
-    uchar ext_type;
     int remainder;
     char dirname[1024] = "";
     int dirnamelen = 0;
+    int pos = 0;
 
-    headersize = get_word(&buf[0]);
+    headersize = get_header(buf, &pos, 2);
 
     fread_crc(buf + 21, 26 - 21, fp);     /* CRC not used */
 
-    get_char(&buf[2], h->method, 5);
-    h->compsize = get_dword(&buf[7]);
-    h->origsize = get_dword(&buf[11]);
-    h->ftime    = get_dword(&buf[15]);
-    /* attrib   = get_byte(&buf[19]); */
-    h->level    = get_byte(&buf[20]); /* header level */
-    h->file_crc = get_word(&buf[21]);
-    h->os_id    = get_byte(&buf[23]);
+    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_word(&buf[24]);
+    ext_headersize = get_header(buf, &pos, 2);
 
-    remainder = headersize - 26;
+    remainder = headersize - pos;
 
     while (ext_headersize != 0) {
-        char *p = extbuf;
+        char extbuf[4096];
+        uchar ext_type;
+        int extpos = 0;
 
         remainder -= ext_headersize;
 
-        fread_crc(p, ext_headersize, fp);
-        ext_type = get_byte(p++);
+        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 */
@@ -369,18 +619,22 @@ read_header_lv2(FILE *fp, char *buf, struct lzh_header *h)
         case 1:
             /* filename header */
             h->namelen = ext_headersize - 3;
-            get_char(p, h->filename, h->namelen);
+            get_string(extbuf, &extpos, h->namelen, h->filename);
             h->filename[h->namelen] = 0;
             break;
         case 2:
             /* dirname header */
             dirnamelen = ext_headersize - 3;
-            get_char(p, dirname, dirnamelen);
+            get_string(extbuf, &extpos, dirnamelen, dirname);
             dirname[dirnamelen] = 0;
             break;
+        default:
+            break;
         }
-        ext_headersize = get_word(&extbuf[ext_headersize - 2]);
+        extpos = ext_headersize - 2;
+        ext_headersize = get_header(extbuf, &extpos, 2);
     }
+
     if (dirnamelen > 0 && dirname[dirnamelen-1] != '/') {
         dirname[dirnamelen++] = '/';
     }
@@ -400,10 +654,7 @@ read_header_lv2(FILE *fp, char *buf, struct lzh_header *h)
 static int
 read_header(FILE *fp, struct lzh_header *h)
 {
-    int headersize;
-    int headersum;
     char buf[4096];
-    int ext_headersize;
     int ret;
 
     ret = fgetc(fp);
@@ -433,29 +684,49 @@ read_header(FILE *fp, struct lzh_header *h)
 void
 write_header_lv0(FILE *fp, struct lzh_header *h)
 {
-    char buf[4096], *p = buf;
+    char buf[4096];
     int sum;
     int headersize;
+    int pos = 0;
 
-    headersize = 22 + h->namelen;
+    headersize = 22;
+    if (!opts.generic)
+        headersize += 12;       /* extended header size */
 
-    put_byte(&buf[0], 0);       /* dummy */
-    put_byte(&buf[1], 0);       /* dummy */
-    put_char(&buf[2], h->method, 5);
-    put_dword(&buf[7], h->compsize);    /* packed size */
-    put_dword(&buf[11], h->origsize);   /* original size */
-    put_dword(&buf[15], h->ftime);      /* ftime */
-    put_byte(&buf[19], 0x20);           /* attribute */
-    put_byte(&buf[20], 0);              /* level */
-    put_byte(&buf[21], h->namelen);     /* length of pathname */
-    put_char(&buf[22], h->filename, h->namelen);
-    put_word(&buf[22+h->namelen], h->file_crc);
+    if (headersize + h->namelen > 255) {
+        warn("path name is too long");
+        h->namelen = 255 - headersize;
+        headersize = 255;
+    }
+    else {
+        headersize += h->namelen;
+    }
 
-    headersize += 0;            /* size of ext-header (old style) */
-    put_byte(&buf[0], headersize);
+    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_byte(&buf[1], sum);
+    put_header_tmp(buf, 1, 1, sum);
 
     fwrite_crc(buf, headersize+2, fp);
 }
@@ -463,87 +734,112 @@ write_header_lv0(FILE *fp, struct lzh_header *h)
 void
 write_header_lv1(FILE *fp, struct lzh_header *h)
 {
-    char buf[4096], *p = buf;
+    char buf[4096];
     int sum;
     int headersize;
-
-    headersize = 25 + h->namelen;
-
-    put_byte(&buf[0], headersize);
-    put_byte(&buf[1], 0);       /* dummy */
-    put_char(&buf[2], h->method, 5);
-    put_dword(&buf[7], h->compsize);    /* packed size */
-    put_dword(&buf[11], h->origsize);   /* original size */
-    put_dword(&buf[15], h->ftime);      /* ftime */
-    put_byte(&buf[19], 0x20);           /* attribute */
-    put_byte(&buf[20], 1);              /* level */
-    put_byte(&buf[21], h->namelen);     /* length of pathname */
-    put_char(&buf[22], h->filename, h->namelen);
-    put_word(&buf[22+h->namelen], h->file_crc);
+    int extsize = 0;
+    int pos = 0;
+    int extpos;
+    char *dirname, *fname;
+    int dirnamelen;
+
+    fname  = xbasename(h->filename);
+    dirname   = h->filename;
+    dirnamelen = fname - dirname;
+    h->namelen = strlen(fname);
+
+    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_byte(&buf[22+h->namelen+2], '\0');
+        put_header(buf, &pos, 1, '\0');
     else
-        put_byte(&buf[22+h->namelen+2], 'U');
-    put_word(&buf[22+h->namelen+3], 0x0000); /* next header size */
+        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_byte(&buf[1], sum);
+    put_header_tmp(buf, 1, 1, sum);
 
-    fwrite_crc(buf, headersize+2, fp);
+    fwrite_crc(buf, headersize+2+extsize, fp);
 }
 
 void
 write_header_lv2(FILE *fp, struct lzh_header *h)
 {
-    char buf[4096], *p = buf, *crcptr;
-    int sum;
-    int headersize, next_headersize;
+    char buf[4096], *crcptr;
+    int headersize;
     extern ushort crctable[];
     char dirname[1024] = "", *fname;
     int dirnamelen, len;
-
-    put_word(&buf[0], 0);       /* dummy */
-    put_char(&buf[2], h->method, 5);
-    put_dword(&buf[7], h->compsize);    /* packed size */
-    put_dword(&buf[11], h->origsize);   /* original size */
-    put_dword(&buf[15], h->ftime);      /* time_t */
-    put_byte(&buf[19], 0x20);           /* DOS attribute (0x20 fixed) */
-    put_byte(&buf[20], 2);              /* level */
-    put_word(&buf[21], h->file_crc);
+    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_byte(&buf[23], '\0');
+        put_header(buf, &pos, 1, '\0');
     else
-        put_byte(&buf[23], 'U');
+        put_header(buf, &pos, 1, 'U');
 
-    headersize = 24;
+    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) */
 
-    /* ext-header */
-    p = buf + headersize;
-    next_headersize = 3 + 2;
-    put_word(p, next_headersize); /* next header size */
-    put_byte(p+2, 0);             /* 0x00: header crc */
-    crcptr = p+3;
-    put_word(p+3, 0);             /* crc (dummy) */
-    headersize += next_headersize;
-
-    fname = basename(h->filename);
+    fname = xbasename(h->filename);
     len = strlen(fname);
 
-    p = buf + headersize;
-    next_headersize = 3 + len;
-    put_word(p, next_headersize);      /* next header size */
-    put_byte(p+2, 1);                  /* 0x01: filename header */
-    put_char(p+3, fname, len); /* filename */
-    headersize += next_headersize;
+    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;
@@ -551,33 +847,29 @@ write_header_lv2(FILE *fp, struct lzh_header *h)
     }
 
     if (*dirname) {
-        p = buf + headersize;
-        next_headersize = 3 + dirnamelen;
-        put_word(p, next_headersize); /* next header size */
-        put_byte(p+2, 2);             /* 0x02: dirname header */
-        put_char(p+3, dirname, dirnamelen);        /* dirname */
-        headersize += next_headersize;
+        put_header(buf, &pos, 2, 3 + dirnamelen);
+        put_header(buf, &pos, 1, 2); /* 0x02: dirname header */
+        put_string(buf, &pos, dirnamelen, dirname); /* dirname */
     }
 
-    p = buf + headersize;
-    next_headersize = 0;
-    put_word(p, next_headersize); /* next header size */
-    headersize += next_headersize;
-    headersize += 2;
+    put_header(buf, &pos, 2, 0); /* next header size (end of header) */
 
     /* padding */
-    if (headersize % 256 == 0) {
-        put_byte(&buf[headersize], 0);
-        headersize++;
+    if (pos % 256 == 0) {
+        put_header(buf, &pos, 1, 0);
     }
+    headersize = pos;
 
-    put_word(&buf[0], headersize);
+    put_header_tmp(buf, 0, 2, headersize);
 
-    crc = INIT_CRC;
-    for (p = buf; p - buf < headersize; p++)
-        UPDATE_CRC(*p);
+    {
+        int i;
 
-    put_word(crcptr, crc);
+        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);
 }
@@ -675,7 +967,6 @@ add_1(int replace_flag, struct lzh_header *h)
     if (replace_flag) {
         if (opts.quiet < 2)
             printf("Replacing %s ", h->filename);
-        skip(arcfile, h);
     }
     else {
         if (opts.quiet < 2)
@@ -720,8 +1011,6 @@ add_1(int replace_flag, struct lzh_header *h)
 static int
 add(int replace_flag, char *filename, int namelen)
 {
-    long headerpos, arcpos;
-    uint r;
     struct lzh_header h;
     struct stat st;
 
@@ -734,6 +1023,7 @@ add(int replace_flag, char *filename, int namelen)
 
     stat(h.filename, &st);
 
+    h.mtime = st.st_mtime;
     if (S_ISDIR(st.st_mode)) {
         DIR *dir;
         struct dirent *ent;
@@ -784,7 +1074,6 @@ static void
 extract(int to_file, struct lzh_header *h)
 {
     int n;
-    uint ext_headersize;
     uchar buffer[MAXDICSIZ];
 
     outfile = NULL;
@@ -853,8 +1142,14 @@ extract(int to_file, struct lzh_header *h)
 
     if (to_file) {
         fprintf(stdout, "\n");
-        if (outfile)
+        if (outfile) {
+            struct utimbuf ut;
+
             fclose(outfile);
+
+            ut.actime = ut.modtime = h->mtime;
+            utime(h->filename, &ut);
+        }
     }
     outfile = NULL;
 }
@@ -922,10 +1217,10 @@ search(int argc, char *argv[], struct lzh_header *h)
     int i;
 
     if (argc == 0)
-        return 1;
+        return -1;
     for (i = 0; i < argc; i++)
-        if (match(h->filename, argv[i]))
-            return 1;
+        if (argv[i] && match(h->filename, argv[i]))
+            return i+1;
     return 0;
 }
 
@@ -943,7 +1238,7 @@ parse_args(int argc, char **argv)
     int c;
 
     for (;;) {
-        int this_option_optind = optind ? optind : 1;
+        /* int this_option_optind = optind ? optind : 1; */
         int option_index = 0;
 
         enum {
@@ -987,6 +1282,7 @@ parse_args(int argc, char **argv)
             break;
         case 'g':
             opts.generic = 1;
+            opts.header_level = 0;
             break;
         case 'o':
             /* compress method */
@@ -1049,7 +1345,7 @@ open_tempfile()
 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;
@@ -1100,12 +1396,24 @@ main(int argc, char *argv[])
 
     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;
+            }
+        }
         for (i = 0; i < argc; i++) {
             add(0, argv[i], strlen(argv[i]));
         }
@@ -1154,8 +1462,6 @@ main(int argc, char *argv[])
 
     /* change directory to extract dir */
     if (cmd == 'x') {
-        struct stat *stbuf;
-
         if (opts.outdir) {
             if (mkdir(opts.outdir, 0777) == -1) {
                 if (errno != EEXIST)
@@ -1178,23 +1484,30 @@ main(int argc, char *argv[])
 
         found = search(argc, argv, &h);
         switch (cmd) {
-        case 'r':
-            if (found) {
-                if (add(1, *argv, strlen(*argv)))
+        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(arcfile, outfile, &h);
             }
             else
                 copy(arcfile, outfile, &h);
             break;
-        case 'a':
         case 'd':
             if (found) {
-                if (cmd == 'd') {
-                    count++;
-                    message("'%s' deleted", h.filename);
-                }
+                count++;
+                message("'%s' deleted", h.filename);
                 skip(arcfile, &h);
             }
             else
@@ -1202,7 +1515,7 @@ main(int argc, char *argv[])
             break;
         case 'x':
         case 'p':
-            if (found) {
+            if (found != 0) {
                 extract(cmd == 'x', &h);
                 if (++count == nfiles)
                     done = 1;
@@ -1212,7 +1525,7 @@ main(int argc, char *argv[])
             break;
         case 'l':
         case 'v':
-            if (found) {
+            if (found != 0) {
                 if (count == 0)
                     list_start();
                 list(&h);
@@ -1224,25 +1537,41 @@ main(int argc, char *argv[])
         }
     }
 
+    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 (cmd == 'd') {
+    if (count > 0 && (cmd == 'd' || cmd == 'a' || cmd == 'u')) {
         fputc(0, outfile);      /* end of archive */
         if (ferror(outfile))
             error("Can't write");
         if (!opts.archive_to_stdio)
             unlink(archive_file);
         fclose(outfile);
-        if (arc_count > count) {
+        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);
         }
-        else
-            message("The archive file \"%s\" was removed because it would be empty.", archive_file);
-
         exit(0);
     }
     return EXIT_SUCCESS;