OSDN Git Service

Should not use the basename() function.
[lha/olha.git] / ar.c
diff --git a/ar.c b/ar.c
index 745472d..585e43d 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"
@@ -46,34 +48,29 @@ Structure of archive block (low order byte first):
 #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"
 
-struct lzh_header {
-    char filename[1024];
-    int  namelen;
-    char method[5];
-    int compsize;
-    int origsize;
-    int ftime;
-    int file_crc;
-    char os_id;
-};
+extern char *basename(const char *);
 
 struct lha_method methods[] = {
-           /* id, dicbit, pbit, maxmatch */
-           /* note: dicbit == 0 means no compress */
-    /*0*/ {"-lh0-", 0,  0,  0},        /* no compress */
-    /*1*/ {"-lh1-", 12, 0, 60},        /* 2^12 =  4KB dynamic huffman (LHarc) */
-    /*2*/ {"-lh2-", 13, 0,256},        /* 2^13 =  8KB dynamic huffman */
-    /*3*/ {"-lh3-", 13, 0,256},        /* 2^13 =  8KB static huffman */
-    /*4*/ {"-lh4-", 12, 4,256},        /* 2^12 =  4KB static huffman (pos and len)*/
-    /*5*/ {"-lh5-", 13, 4,256},        /* 2^13 =  8KB static huffman (pos and len)*/
-    /*6*/ {"-lh6-", 15, 5,256},        /* 2^15 = 32KB static huffman (pos and len)*/
-    /*7*/ {"-lh7-", 16, 5,256},        /* 2^16 = 64KB static huffman (pos and len)*/
-    /*8*/ {"-lzs-", 11, 0, 17},        /* 2^11 =  2KB (LArc) */
-    /*9*/ {"-lz5-", 12, 0, 17},        /* 2^12 =  4KB (LArc) */
-    /*10*/{"-lz4-", 0,  0,  0},        /* no compress (LArc) */
-    /*11*/{"-lzd-", 0,  0,  0},        /* directory */
+    /* 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;
@@ -91,28 +88,24 @@ which_method(char *id)
     return NULL;
 }
 
-init_opts()
-{
-    opts.nocompress = 0;
-    opts.outdir = NULL;
-    opts.quiet = 0;
-
-    /* default is the -lh5- method */
-    opts.method   = &methods[5];
-}
-
 #define FNAME_MAX (255 - 25)    /* max strlen(filename) */
 
 int unpackable;                 /* global, set in io.c */
 ulong compsize, origsize;       /* global */
 
-static uchar headersize;
 static char *temp_name;
 
 static void
 print_usage()
 {
-    puts("usage: ...");
+    printf("%s", usage);
+    exit(0);
+}
+
+static void
+print_version()
+{
+    printf("version %s\n", version);
     exit(0);
 }
 
@@ -135,26 +128,84 @@ 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(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)
 {
-    ulong s;
+    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)
+{
+    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)
 {
@@ -167,6 +218,7 @@ calc_headersum(char *buf, int size)
     return s & 0xFF;
 }
 
+#if 0
 int
 get_byte(char *buf)
 {
@@ -222,46 +274,407 @@ 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(FILE *fp, struct lzh_header *h)
+read_header_lv0(FILE *fp, char *buf, struct lzh_header *h)
 {
     int headersize;
     int headersum;
-    char buf[4096];
     int ext_headersize;
+    int pos = 0;
+    int ext_size;
 
-    headersize = (uchar) fgetc(fp);
-    if (headersize == 0)
-        return 0;               /* end of archive */
-    headersum = (uchar) fgetc(fp);
-    fread_crc(buf, headersize, fp);     /* CRC not used */
-    if (calc_headersum(buf, headersize) != headersum)
+    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");
+
+    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 */
+}
+
+/*
+ * 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_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]); */
-    /* level    = get_byte(&buf[18]); */         /* 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;
+    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 */
@@ -269,35 +682,226 @@ read_header(FILE *fp, struct lzh_header *h)
 
 
 void
-write_header(FILE *fp, int headersize, struct lzh_header *h)
+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;
+    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_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);
-    put_byte(&buf[22+h->namelen+2], 'M');
-    put_word(&buf[22+h->namelen+3], 0x0000); /* next header size */
+    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);
 }
 
+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  = 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_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 = xbasename(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) {
+            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(FILE *fp, struct lzh_header *h)
 {
-    fseek(fp, h->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
@@ -306,7 +910,7 @@ copy(FILE *arcfile, FILE *outfile, struct lzh_header *h)
     uint n;
     uchar buffer[MAXDICSIZ];
 
-    write_header(outfile, headersize, h);
+    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)
@@ -333,34 +937,44 @@ store(void)
 }
 
 static int
-add(int replace_flag, char *filename)
+add_dir(int replace_flag, struct lzh_header *h)
+{
+    long headerpos, arcpos;
+    uint r;
+
+    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;
-    struct lzh_header h;
 
-    if ((infile = fopen(filename, "rb")) == NULL) {
-        fprintf(stderr, "Can't open %s\n", filename);
+    if ((infile = fopen(h->filename, "rb")) == NULL) {
+        fprintf(stderr, "Can't open %s\n", h->filename);
         return 0;               /* failure */
     }
     if (replace_flag) {
         if (opts.quiet < 2)
-            printf("Replacing %s ", filename);
-        skip(arcfile, &h);
+            printf("Replacing %s ", h->filename);
     }
     else {
         if (opts.quiet < 2)
-            printf("Adding %s ", filename);
+            printf("Adding %s ", h->filename);
     }
 
-    strcpy(h.filename, filename);
-    h.namelen = strlen(filename);
-
-    headersize = 25 + h.namelen;
-    memcpy(h.method, opts.method->id, sizeof(h.method));  /* compress */
-
     headerpos = ftell(outfile);
-    write_header(outfile, headersize, &h);
+    write_header(outfile, h);
     arcpos = ftell(outfile);
 
     origsize = compsize = 0;
@@ -374,19 +988,19 @@ add(int replace_flag, char *filename)
     }
 
     if (unpackable) {
-        h.method[3] = '0';        /* store */
+        memcpy(h->method, "-lh0-", sizeof(h->method));  /* store */
         rewind(infile);
         fseek(outfile, arcpos, SEEK_SET);
         store();
     }
-    h.file_crc = crc ^ INIT_CRC;
+    h->file_crc = crc ^ INIT_CRC;
     fclose(infile);
 
-    h.compsize = compsize;
-    h.origsize = origsize;
+    h->compsize = compsize;
+    h->origsize = origsize;
 
     fseek(outfile, headerpos, SEEK_SET);
-    write_header(outfile, headersize, &h);
+    write_header(outfile, h);
     fseek(outfile, 0L, SEEK_END);
     r = ratio(compsize, origsize);
     if (opts.quiet < 2)
@@ -394,6 +1008,55 @@ add(int replace_flag, char *filename)
     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)
 {
@@ -411,18 +1074,36 @@ static void
 extract(int to_file, struct lzh_header *h)
 {
     int n;
-    uint ext_headersize;
     uchar buffer[MAXDICSIZ];
 
+    outfile = NULL;
+
     if (to_file) {
-        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;
+        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);
+            }
+        }
+        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);
             }
-            h->namelen = strlen(h->filename);
         }
         if (opts.quiet < 2)
             printf("Extracting %s ", h->filename);
@@ -455,19 +1136,37 @@ extract(int to_file, struct lzh_header *h)
             h->origsize -= n;
         }
     }
-    if (to_file)
-        fclose(outfile);
-    else
-        outfile = NULL;
+
     if ((crc ^ INIT_CRC) != h->file_crc)
-        fprintf(stderr, "CRC error\n");
+        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)
 {
     if (opts.quiet < 2)
-        printf("Filename         Original Compressed Ratio CRC Method\n");
+        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
@@ -479,8 +1178,15 @@ list(struct lzh_header *h)
     if (h->namelen > 14)
         printf("\n              ");
     r = ratio(h->compsize, h->origsize);
-    printf(" %10lu %10lu %u.%03u %04X %5.5s\n",
-           h->origsize, h->compsize, r / 1000, r % 1000, h->file_crc, h->method);
+    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
@@ -511,17 +1217,16 @@ 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;
 }
 
 static void
 exitfunc(void)
 {
-    fclose(outfile);
     remove(temp_name);
 }
 
@@ -533,11 +1238,12 @@ 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 {
             LHA_OPT_HELP = 128,
+            LHA_OPT_VERSION,
         };
 
         static struct option long_options[] = {
@@ -551,35 +1257,57 @@ parse_args(int argc, char **argv)
                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, "o[567]q[012]w:z",
+        c = getopt_long(argc, argv, "012fgo[567]q[012]vw:z",
                         long_options, &option_index);
 
-        if (c == -1) break;
+        if (c == -1) break;     /* end of parsing options */
 
         switch (c) {
-        case 0:                 /* set vallue by long option */
+        case '?':
+            print_usage();
             break;
-        case 'o':               /* compress method */
+        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;
+                int idx = 1;    /* -o means -lh1- method */
 
                 if (optarg)
                     idx = *optarg - '0'; /* -lh[567]- method */
-                else
-                    idx = 1;    /* -lh1- method */
 
                 opts.method   = &methods[idx];
             }
             break;
-        case 'q':               /* quiet mode */
-            opts.quiet = 2;     /* level 2 */
+        case 'q':
+            /* quiet mode */
+            opts.quiet = 2;     /* -q is equivalent to -q2 */
             if (optarg)
                 opts.quiet = *optarg - '0';
             break;
-        case 'w':               /* extract directory */
+        case 'v':
+            /* verbose mode */
+            opts.verbose = 1;
+            break;
+
+        case 'w':
+            /* extract directory */
             if (!optarg)
                 error("extract directory does not specified for `-w'");
             if (*optarg == '=')
@@ -593,6 +1321,9 @@ parse_args(int argc, char **argv)
         case LHA_OPT_HELP:
             print_usage();
             break;
+        case LHA_OPT_VERSION:
+            print_version();
+            break;
         default:
             break;
         }
@@ -614,11 +1345,12 @@ 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;
 
-    init_opts();
+    INITIALIZE_OPTS(opts);
 
     if (argv[1] == 0)
         print_usage();
@@ -651,6 +1383,9 @@ main(int argc, char *argv[])
 
     archive_file = argv[0];
 
+    if (strcmp(archive_file, "-") == 0)
+        opts.archive_to_stdio = 1;
+
     argv++;
     argc--;
 
@@ -661,34 +1396,63 @@ 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]);
+            add(0, argv[i], strlen(argv[i]));
         }
 
         fputc(0, outfile);      /* end of archive */
         if (ferror(outfile))
             error("Can't write");
-        remove(archive_file);
-        if (rename(temp_name, archive_file) == -1)
-            error("fail to rename()");
-
+        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. */
-        arcfile = fopen(archive_file, "rb");
+        if (opts.archive_to_stdio) {
+            arcfile = stdin;
+        }
+        else {
+            arcfile = fopen(archive_file, "rb");
+        }
         if (arcfile == NULL)
-            error("Can't open archive '%s'", argv[2]);
+            error("Can't open archive '%s'", archive_file);
 
         break;
     default:
@@ -698,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)
@@ -711,27 +1473,41 @@ main(int argc, char *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, *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) {
-                count += (cmd == 'D');
+                count++;
+                message("'%s' deleted", h.filename);
                 skip(arcfile, &h);
             }
             else
@@ -739,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;
@@ -749,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);
@@ -761,9 +1537,42 @@ 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 (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);
+        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);
+    }
     return EXIT_SUCCESS;
 }