]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add support for writing mtree classic format, which mtree utility generates by default.
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Fri, 21 Sep 2012 02:51:16 +0000 (11:51 +0900)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Mon, 24 Sep 2012 22:32:34 +0000 (07:32 +0900)
Makefile.am
libarchive/archive.h
libarchive/archive_write_set_format_by_name.c
libarchive/archive_write_set_format_mtree.c
libarchive/test/test_write_format_mtree.c
libarchive/test/test_write_format_mtree_classic.c [new file with mode: 0644]
libarchive/test/test_write_format_mtree_classic_indent.c [new file with mode: 0644]
libarchive/test/test_write_format_mtree_fflags.c

index a35e55f10acdba1c0702fb3c03c59e6e25c53d4a..fe30c5b66afea1fbb46a34f9d55083789ace3ded 100644 (file)
@@ -426,6 +426,8 @@ libarchive_test_SOURCES=                                    \
        libarchive/test/test_write_format_iso9660_filename.c    \
        libarchive/test/test_write_format_iso9660_zisofs.c      \
        libarchive/test/test_write_format_mtree.c               \
+       libarchive/test/test_write_format_mtree_classic.c       \
+       libarchive/test/test_write_format_mtree_classic_indent.c\
        libarchive/test/test_write_format_mtree_fflags.c        \
        libarchive/test/test_write_format_mtree_no_separator.c  \
        libarchive/test/test_write_format_mtree_absolute_path.c \
index 15c0daee3ea9f16a4ef60b098d2f1b44dfd80cd7..07081a5aa39d61cd0c48c12f1183c41f8d404129 100644 (file)
@@ -614,6 +614,7 @@ __LA_DECL int archive_write_set_format_cpio_newc(struct archive *);
 __LA_DECL int archive_write_set_format_gnutar(struct archive *);
 __LA_DECL int archive_write_set_format_iso9660(struct archive *);
 __LA_DECL int archive_write_set_format_mtree(struct archive *);
+__LA_DECL int archive_write_set_format_mtree_classic(struct archive *);
 /* TODO: int archive_write_set_format_old_tar(struct archive *); */
 __LA_DECL int archive_write_set_format_pax(struct archive *);
 __LA_DECL int archive_write_set_format_pax_restricted(struct archive *);
index 9671f60f8cd12444966ac89bd587a1b45504e045..1210f35a51ba0e85798f455c09c6393a67fbf4e5 100644 (file)
@@ -56,6 +56,7 @@ struct { const char *name; int (*setter)(struct archive *); } names[] =
        { "iso",        archive_write_set_format_iso9660 },
        { "iso9660",    archive_write_set_format_iso9660 },
        { "mtree",      archive_write_set_format_mtree },
+       { "mtree-classic",      archive_write_set_format_mtree_classic },
        { "newc",       archive_write_set_format_cpio_newc },
        { "odc",        archive_write_set_format_cpio },
        { "pax",        archive_write_set_format_pax },
index dee7c689e6e602e84e75c4cecb548af5277f72c0..63d18460f0ae7c6081dc25de60ba2713753e08aa 100644 (file)
@@ -38,6 +38,8 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_mtree.c 201171
 #include "archive_crypto_private.h"
 #include "archive_entry.h"
 #include "archive_private.h"
+#include "archive_rb.h"
+#include "archive_string.h"
 #include "archive_write_private.h"
 
 #define INDENTNAMELEN  15
@@ -45,27 +47,60 @@ __FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_mtree.c 201171
 #define SET_KEYS       \
        (F_FLAGS | F_GID | F_GNAME | F_MODE | F_TYPE | F_UID | F_UNAME)
 
+struct attr_counter {
+       struct attr_counter *prev;
+       struct attr_counter *next;
+       int count;
+       struct mtree_entry *m_entry;
+};
+
+struct att_counter_set {
+       struct attr_counter *uid_list;
+       struct attr_counter *gid_list;
+       struct attr_counter *mode_list;
+       struct attr_counter *flags_list;
+};
+
+struct mtree_chain {
+       struct mtree_entry *first;
+       struct mtree_entry **last;
+};
+
+struct dir_info {
+       struct archive_rb_tree rbtree;
+       struct att_counter_set acs;
+       struct mtree_chain children;
+};
+
 struct mtree_entry {
+       struct archive_rb_node rbnode;
        struct mtree_entry *next;
-
-       char *pathname;
-       char *symlink;
+       struct mtree_entry *chnext;
+       struct mtree_entry *parent;
+       struct dir_info *dir_info;
+
+       struct archive_string parentdir;
+       struct archive_string basename;
+       struct archive_string pathname;
+       struct archive_string symlink;
+       struct archive_string uname;
+       struct archive_string gname;
+       struct archive_string fflags_text;
        unsigned int nlink;
        mode_t filetype;
        mode_t mode;
+       int64_t size;
        int64_t uid;
        int64_t gid;
-       char *uname;
-       char *gname;
-       char *fflags_text;
-       unsigned long fflags_set;
-       unsigned long fflags_clear;
        time_t mtime;
        long mtime_nsec;
+       unsigned long fflags_set;
+       unsigned long fflags_clear;
        dev_t rdevmajor;
        dev_t rdevminor;
-       int64_t size;
 
+       int dir_depth;
+       int virtual_dir;
        int compute_sum;
        uint32_t crc;
 #ifdef ARCHIVE_HAS_MD5
@@ -88,23 +123,23 @@ struct mtree_entry {
 #endif
 };
 
-struct attr_counter {
-       struct attr_counter *prev;
-       struct attr_counter *next;
-       int count;
-       struct mtree_entry *m_entry;
-};
-
 struct mtree_writer {
        struct mtree_entry *mtree_entry;
+       struct mtree_entry *root;
+       struct mtree_entry *cur_dirent;
+       struct archive_string cur_dirstr;
+       struct mtree_chain file_list;
+
        struct archive_string ebuf;
        struct archive_string buf;
        int first;
        uint64_t entry_bytes_remaining;
+
+       /*
+        * Set global value.
+        */
        struct {
-               int             output;
-               int             processed;
-               struct archive_string parent;
+               int             processing;
                mode_t          type;
                int             keys;
                int64_t         uid;
@@ -112,14 +147,11 @@ struct mtree_writer {
                mode_t          mode;
                unsigned long   fflags_set;
                unsigned long   fflags_clear;
-
-               struct attr_counter *uid_list;
-               struct attr_counter *gid_list;
-               struct attr_counter *mode_list;
-               struct attr_counter *flags_list;
-               struct mtree_entry *me_first;
-               struct mtree_entry **me_last;
        } set;
+       struct att_counter_set all_acs;
+       int classic;
+       int depth;
+
        /* check sum */
        int compute_sum;
        uint32_t crc;
@@ -177,23 +209,51 @@ struct mtree_writer {
                                 * directory type files. like mtree(8) -d option.
                                 */
        int indent;             /* if the indent is 1, indent writing data. */
+       int output_global_set;
 };
 
 #define DEFAULT_KEYS   (F_DEV | F_FLAGS | F_GID | F_GNAME | F_SLINK | F_MODE\
                         | F_NLINK | F_SIZE | F_TIME | F_TYPE | F_UID\
                         | F_UNAME)
 
-static struct attr_counter * new_attr_count(struct mtree_entry *,
-       struct attr_counter *);
-static void free_attr_count(struct attr_counter **);
-static int inc_attr_count(struct attr_counter **, struct attr_counter *,
+static int attr_counter_dec(struct attr_counter **, struct attr_counter *);
+static void attr_counter_free(struct attr_counter **);
+static int attr_counter_inc(struct attr_counter **, struct attr_counter *,
        struct attr_counter *, struct mtree_entry *);
-static int collect_set_values(struct mtree_writer *, struct mtree_entry *);
+static struct attr_counter * attr_counter_new(struct mtree_entry *,
+       struct attr_counter *);
+static int attr_counter_set_collect(struct mtree_writer *,
+       struct mtree_entry *);
+static int attr_counter_set_collect_sub(struct mtree_writer *,
+       struct mtree_entry *, struct att_counter_set *);
+static void attr_counter_set_free(struct att_counter_set *);
+static int attr_counter_set_remove(struct mtree_writer *, struct mtree_entry *);
+static int attr_counter_set_remove_sub(struct mtree_writer *,
+       struct mtree_entry *, struct att_counter_set *);
 static int get_keys(struct mtree_writer *, struct mtree_entry *);
 static void sum_init(struct mtree_writer *);
 static void sum_update(struct mtree_writer *, const void *, size_t);
 static void sum_final(struct mtree_writer *, struct mtree_entry *);
 static void sum_write(struct archive_string *, struct mtree_entry *);
+static int mtree_entry_add_child_tail(struct mtree_entry *,
+       struct mtree_entry *);
+static int mtree_entry_create_virtual_dir(struct archive_write *, const char *,
+       struct mtree_entry **);
+static int mtree_entry_cmp_node(const struct archive_rb_node *,
+       const struct archive_rb_node *);
+static int mtree_entry_cmp_key(const struct archive_rb_node *, const void *);
+static int mtree_entry_exchange_same_entry(struct archive_write *,
+    struct mtree_entry *, struct mtree_entry *);
+static void mtree_entry_free(struct mtree_entry *);
+static int mtree_entry_new(struct archive_write *, struct archive_entry *,
+       struct mtree_entry **);
+static void mtree_entry_register_free(struct mtree_writer *);
+static void mtree_entry_register_init(struct mtree_writer *);
+static int mtree_entry_setup_filenames(struct archive_write *,
+       struct mtree_entry *, struct archive_entry *);
+static int mtree_entry_tree(struct archive_write *, struct mtree_entry *);
+static int write_mtree_entry(struct archive_write *, struct mtree_entry *);
+static int write_dot_dot_entry(struct archive_write *, struct mtree_entry *);
 
 #define        COMPUTE_CRC(var, ch)    (var) = (var) << 8 ^ crctab[(var) >> 24 ^ (ch)]
 static const uint32_t crctab[] = {
@@ -303,9 +363,19 @@ mtree_quote(struct archive_string *s, const char *str)
 static void
 mtree_indent(struct mtree_writer *mtree)
 {
-       int i, fn;
+       int i, fn, nd, pd;
        const char *r, *s, *x;
 
+       if (mtree->classic) {
+               if (mtree->indent) {
+                       nd = 0;
+                       pd = mtree->depth * 4;
+               } else {
+                       nd = mtree->depth?4:0;
+                       pd = 0;
+               }
+       } else
+               nd = pd = 0;
        fn = 1;
        s = r = mtree->ebuf.s;
        x = NULL;
@@ -314,37 +384,45 @@ mtree_indent(struct mtree_writer *mtree)
        while ((r = strchr(r, ' ')) != NULL) {
                if (fn) {
                        fn = 0;
+                       for (i = 0; i < nd + pd; i++)
+                               archive_strappend_char(&mtree->buf, ' ');
                        archive_strncat(&mtree->buf, s, r - s);
-                       if (r -s > INDENTNAMELEN) {
+                       if (nd + (r -s) > INDENTNAMELEN) {
                                archive_strncat(&mtree->buf, " \\\n", 3);
-                               for (i = 0; i < (INDENTNAMELEN + 1); i++)
+                               for (i = 0; i < (INDENTNAMELEN + 1 + pd); i++)
                                        archive_strappend_char(&mtree->buf, ' ');
                        } else {
-                               for (i = r -s; i < (INDENTNAMELEN + 1); i++)
+                               for (i = r -s + nd; i < (INDENTNAMELEN + 1); i++)
                                        archive_strappend_char(&mtree->buf, ' ');
                        }
                        s = ++r;
                        x = NULL;
                        continue;
                }
-               if (r - s <= MAXLINELEN - 3 - INDENTNAMELEN)
+               if (pd + (r - s) <= MAXLINELEN - 3 - INDENTNAMELEN)
                        x = r++;
                else {
                        if (x == NULL)
                                x = r;
                        archive_strncat(&mtree->buf, s, x - s);
                        archive_strncat(&mtree->buf, " \\\n", 3);
-                       for (i = 0; i < (INDENTNAMELEN + 1); i++)
+                       for (i = 0; i < (INDENTNAMELEN + 1 + pd); i++)
                                archive_strappend_char(&mtree->buf, ' ');
                        s = r = ++x;
                        x = NULL;
                }
        }
-       if (x != NULL && strlen(s) > MAXLINELEN - 3 - INDENTNAMELEN) {
+       if (fn) {
+               for (i = 0; i < nd + pd; i++)
+                       archive_strappend_char(&mtree->buf, ' ');
+               archive_strcat(&mtree->buf, s);
+               s += strlen(s);
+       }
+       if (x != NULL && pd + strlen(s) > MAXLINELEN - 3 - INDENTNAMELEN) {
                /* Last keyword is longer. */
                archive_strncat(&mtree->buf, s, x - s);
                archive_strncat(&mtree->buf, " \\\n", 3);
-               for (i = 0; i < (INDENTNAMELEN + 1); i++)
+               for (i = 0; i < (INDENTNAMELEN + 1 + pd); i++)
                        archive_strappend_char(&mtree->buf, ' ');
                s = ++x;
        }
@@ -352,162 +430,58 @@ mtree_indent(struct mtree_writer *mtree)
        archive_string_empty(&mtree->ebuf);
 }
 
-#if !defined(_WIN32) || defined(__CYGWIN__)
-static size_t
-dir_len(struct mtree_entry *me)
-{
-       const char *path, *r;
-
-       path = me->pathname;
-       r = strrchr(path, '/');
-       if (r == NULL)
-               return (0);
-       /* Include a separator size */
-       return (r - path + 1);
-}
-
-#else /* _WIN32 && !__CYGWIN__ */
-/*
- * Note: We should use wide-character for findng '\' character,
- * a directory separator on Windows, because some character-set have
- * been using the '\' character for a part of its multibyte character
- * code.
- */
-static size_t
-dir_len(struct mtree_entry *me)
-{
-       wchar_t wc;
-       const char *path;
-       const char *p, *rp;
-       size_t al, l, size;
-
-       path = me->pathname;
-       al = l = -1;
-       for (p = path; *p != '\0'; ++p) {
-               if (*p == '\\')
-                       al = l = p - path;
-               else if (*p == '/')
-                       al = p - path;
-       }
-       if (l == (size_t)-1)
-               goto alen;
-       size = p - path;
-       rp = p = path;
-       while (*p != '\0') {
-               l = mbtowc(&wc, p, size);
-               if (l == (size_t)-1)
-                       goto alen;
-               if (l == 1 && (wc == L'/' || wc == L'\\'))
-                       rp = p;
-               p += l;
-               size -= l;
-       }
-       return (rp - path + 1);
-alen:
-       if (al == (size_t)-1)
-               return (0);
-       return (al + 1);
-}
-#endif /* _WIN32 && !__CYGWIN__ */
-
-/*
- * Test if a parent directory of the current entry is changed.
- */
-static int
-parent_dir_changed(struct archive_string *dir, struct mtree_entry *me)
-{
-       const char *path;
-       size_t l;
-
-       l = dir_len(me);
-       path = me->pathname;
-       if (archive_strlen(dir) > 0) {
-               if (l == 0) {
-                       archive_string_empty(dir);
-                       return (1);
-               }
-               if (strncmp(dir->s, path, l) == 0)
-                       return (0); /* The parent directory is the same. */
-       } else if (l == 0)
-               return (0);         /* The parent directory is the same. */
-       archive_strncpy(dir, path, l);
-       return (1);
-}
-
 /*
  * Write /set keyword.
  * Set most used value of uid,gid,mode and fflags, which are
- * collected by collect_set_values() function.
+ * collected by attr_counter_set_collect() function.
  */
 static void
-write_global(struct mtree_writer *mtree)
+write_global(struct mtree_writer *mtree, struct att_counter_set *acs)
 {
        struct archive_string setstr;
        struct archive_string unsetstr;
-       const char *name;
        int keys, oldkeys, effkeys;
-       struct attr_counter *ac;
 
        archive_string_init(&setstr);
        archive_string_init(&unsetstr);
        keys = mtree->keys & SET_KEYS;
        oldkeys = mtree->set.keys;
        effkeys = keys;
-       if (mtree->set.processed) {
+       if (mtree->set.processing) {
                /*
                 * Check if the global data needs updating.
                 */
                effkeys &= ~F_TYPE;
-               if (oldkeys & (F_UNAME | F_UID)) {
-                       ac = mtree->set.uid_list;
-                       do {
-                               if (mtree->set.uid == ac->m_entry->uid) {
-                                       effkeys &= ~(F_UNAME | F_UID);
-                                       break;
-                               }
-                               if (ac->next != NULL &&
-                                   ac->next->count == ac->count)
-                                       continue;
-                       } while (0);
+               if (acs->uid_list == NULL)
+                       effkeys &= ~(F_UNAME | F_UID);
+               else if (oldkeys & (F_UNAME | F_UID)) {
+                       if (acs->uid_list->count < 2 ||
+                           mtree->set.uid == acs->uid_list->m_entry->uid)
+                               effkeys &= ~(F_UNAME | F_UID);
                }
-               if (oldkeys & (F_GNAME | F_GID)) {
-                       ac = mtree->set.gid_list;
-                       do {
-                               if (mtree->set.gid == ac->m_entry->gid) {
-                                       effkeys &= ~(F_GNAME | F_GID);
-                                       break;
-                               }
-                               if (ac->next != NULL &&
-                                   ac->next->count == ac->count)
-                                       continue;
-                       } while (0);
+               if (acs->gid_list == NULL)
+                       effkeys &= ~(F_GNAME | F_GID);
+               else if (oldkeys & (F_GNAME | F_GID)) {
+                       if (acs->gid_list->count < 2 ||
+                           mtree->set.gid == acs->gid_list->m_entry->gid)
+                               effkeys &= ~(F_GNAME | F_GID);
                }
-               if (oldkeys & F_MODE) {
-                       ac = mtree->set.mode_list;
-                       do {
-                               if (mtree->set.mode == ac->m_entry->mode) {
-                                       effkeys &= ~F_MODE;
-                                       break;
-                               }
-                               if (ac->next != NULL &&
-                                   ac->next->count == ac->count)
-                                       continue;
-                       } while (0);
+               if (acs->mode_list == NULL)
+                       effkeys &= ~F_MODE;
+               else if (oldkeys & F_MODE) {
+                       if (acs->mode_list->count < 2 ||
+                           mtree->set.mode == acs->mode_list->m_entry->mode)
+                               effkeys &= ~F_MODE;
                }
-               if ((oldkeys & F_FLAGS) != 0) {
-                       ac = mtree->set.flags_list;
-                       do {
-                               if (ac->m_entry->fflags_set ==
-                                       mtree->set.fflags_set &&
-                                   ac->m_entry->fflags_clear ==
-                                       mtree->set.fflags_clear) {
-                                       effkeys &= ~F_FLAGS;
-                                       break;
-                               }
-                               if (ac->next != NULL &&
-                                   ac->next->count == ac->count)
-                                       continue;
-                       } while (0);
+               if (acs->flags_list == NULL)
+                       effkeys &= ~F_FLAGS;
+               else if ((oldkeys & F_FLAGS) != 0) {
+                       if (acs->flags_list->count < 2 ||
+                           (acs->flags_list->m_entry->fflags_set ==
+                               mtree->set.fflags_set &&
+                            acs->flags_list->m_entry->fflags_clear ==
+                               mtree->set.fflags_clear))
+                               effkeys &= ~F_FLAGS;
                }
        }
        if ((keys & effkeys & F_TYPE) != 0) {
@@ -520,10 +494,9 @@ write_global(struct mtree_writer *mtree)
                }
        }
        if ((keys & effkeys & F_UNAME) != 0) {
-               name = mtree->set.uid_list->m_entry->uname;
-               if (name != NULL) {
+               if (archive_strlen(&(acs->uid_list->m_entry->uname)) > 0) {
                        archive_strcat(&setstr, " uname=");
-                       mtree_quote(&setstr, name);
+                       mtree_quote(&setstr, acs->uid_list->m_entry->uname.s);
                } else {
                        keys &= ~F_UNAME;
                        if ((oldkeys & F_UNAME) != 0)
@@ -531,15 +504,14 @@ write_global(struct mtree_writer *mtree)
                }
        }
        if ((keys & effkeys & F_UID) != 0) {
-               mtree->set.uid = mtree->set.uid_list->m_entry->uid;
+               mtree->set.uid = acs->uid_list->m_entry->uid;
                archive_string_sprintf(&setstr, " uid=%jd",
                    (intmax_t)mtree->set.uid);
        }
        if ((keys & effkeys & F_GNAME) != 0) {
-               name = mtree->set.gid_list->m_entry->gname;
-               if (name != NULL) {
+               if (archive_strlen(&(acs->gid_list->m_entry->gname)) > 0) {
                        archive_strcat(&setstr, " gname=");
-                       mtree_quote(&setstr, name);
+                       mtree_quote(&setstr, acs->gid_list->m_entry->gname.s);
                } else {
                        keys &= ~F_GNAME;
                        if ((oldkeys & F_GNAME) != 0)
@@ -547,24 +519,25 @@ write_global(struct mtree_writer *mtree)
                }
        }
        if ((keys & effkeys & F_GID) != 0) {
-               mtree->set.gid = mtree->set.gid_list->m_entry->gid;
+               mtree->set.gid = acs->gid_list->m_entry->gid;
                archive_string_sprintf(&setstr, " gid=%jd",
                    (intmax_t)mtree->set.gid);
        }
        if ((keys & effkeys & F_MODE) != 0) {
-               mtree->set.mode = mtree->set.mode_list->m_entry->mode;
+               mtree->set.mode = acs->mode_list->m_entry->mode;
                archive_string_sprintf(&setstr, " mode=%o",
                    (unsigned int)mtree->set.mode);
        }
        if ((keys & effkeys & F_FLAGS) != 0) {
-               name = mtree->set.flags_list->m_entry->fflags_text;
-               if (name != NULL) {
+               if (archive_strlen(
+                   &(acs->flags_list->m_entry->fflags_text)) > 0) {
                        archive_strcat(&setstr, " flags=");
-                       mtree_quote(&setstr, name);
+                       mtree_quote(&setstr,
+                           acs->flags_list->m_entry->fflags_text.s);
                        mtree->set.fflags_set =
-                           mtree->set.flags_list->m_entry->fflags_set;
+                           acs->flags_list->m_entry->fflags_set;
                        mtree->set.fflags_clear =
-                           mtree->set.flags_list->m_entry->fflags_clear;
+                           acs->flags_list->m_entry->fflags_clear;
                } else {
                        keys &= ~F_FLAGS;
                        if ((oldkeys & F_FLAGS) != 0)
@@ -578,16 +551,11 @@ write_global(struct mtree_writer *mtree)
                archive_string_sprintf(&mtree->buf, "/set%s\n", setstr.s);
        archive_string_free(&setstr);
        mtree->set.keys = keys;
-       mtree->set.processed = 1;
-
-       free_attr_count(&mtree->set.uid_list);
-       free_attr_count(&mtree->set.gid_list);
-       free_attr_count(&mtree->set.mode_list);
-       free_attr_count(&mtree->set.flags_list);
+       mtree->set.processing = 1;
 }
 
 static struct attr_counter *
-new_attr_count(struct mtree_entry *me, struct attr_counter *prev)
+attr_counter_new(struct mtree_entry *me, struct attr_counter *prev)
 {
        struct attr_counter *ac;
 
@@ -602,7 +570,7 @@ new_attr_count(struct mtree_entry *me, struct attr_counter *prev)
 }
 
 static void
-free_attr_count(struct attr_counter **top)
+attr_counter_free(struct attr_counter **top)
 {
        struct attr_counter *ac, *tac;
 
@@ -618,7 +586,7 @@ free_attr_count(struct attr_counter **top)
 }
 
 static int
-inc_attr_count(struct attr_counter **top, struct attr_counter *ac,
+attr_counter_inc(struct attr_counter **top, struct attr_counter *ac,
     struct attr_counter *last, struct mtree_entry *me)
 {
        struct attr_counter *pac;
@@ -647,7 +615,7 @@ inc_attr_count(struct attr_counter **top, struct attr_counter *ac,
                        ac->next->prev = ac;
                }
        } else {
-               ac = new_attr_count(me, last);
+               ac = attr_counter_new(me, last);
                if (ac == NULL)
                        return (-1);
                last->next = ac;
@@ -656,90 +624,204 @@ inc_attr_count(struct attr_counter **top, struct attr_counter *ac,
 }
 
 static int
-collect_set_values(struct mtree_writer *mtree, struct mtree_entry *me)
+attr_counter_dec(struct attr_counter **top, struct attr_counter *ac)
+{
+       struct attr_counter *pac, *lpac;
+
+       if (ac == NULL)
+               return (0);
+
+       ac->count--;
+       if (ac->next == NULL || ac->next->count <= ac->count)
+               return (0);
+       for (lpac = pac = ac->next; pac; pac = pac->next) {
+               if (pac->count <= ac->count)
+                       break;
+               lpac = pac;
+       }
+       if (ac->prev == NULL)
+               *top = ac->next;
+       else
+               ac->prev->next = ac->next;
+       ac->next->prev = ac->prev;
+       if (pac != NULL) {
+               ac->prev = pac->prev;
+               ac->next = pac;
+               pac->prev = ac;
+       } else {
+               ac->prev = lpac;
+               ac->next = NULL;
+       }
+       return (0);
+}
+
+/*
+ * Tabulate uid,gid,mode and fflags of a entry in order to be used for /set.
+ */
+static int
+attr_counter_set_collect(struct mtree_writer *mtree, struct mtree_entry *me)
+{
+
+       /* Collect global set values for all entries. */
+       if (attr_counter_set_collect_sub(mtree, me, &mtree->all_acs) < 0)
+               return (-1);
+       if (me->dir_depth > 0) {
+               if (attr_counter_set_collect_sub(mtree, me,
+                       &me->parent->dir_info->acs) < 0)
+                       return (-1);
+       }
+       return (0);
+}
+
+static int
+attr_counter_set_collect_sub(struct mtree_writer *mtree, struct mtree_entry *me,
+    struct att_counter_set *acs)
 {
        int keys = mtree->keys;
        struct attr_counter *ac, *last;
 
        if (keys & (F_UNAME | F_UID)) {
-               if (mtree->set.uid_list == NULL) {
-                       mtree->set.uid_list = new_attr_count(me, NULL);
-                       if (mtree->set.uid_list == NULL)
+               if (acs->uid_list == NULL) {
+                       acs->uid_list = attr_counter_new(me, NULL);
+                       if (acs->uid_list == NULL)
                                return (-1);
                } else {
                        last = NULL;
-                       for (ac = mtree->set.uid_list; ac; ac = ac->next) {
+                       for (ac = acs->uid_list; ac; ac = ac->next) {
                                if (ac->m_entry->uid == me->uid)
                                        break;
                                last = ac;
                        }
-                       if (inc_attr_count(
-                           &mtree->set.uid_list, ac, last, me) < 0)
+                       if (attr_counter_inc(&acs->uid_list, ac, last, me) < 0)
                                return (-1);
                }
        }
        if (keys & (F_GNAME | F_GID)) {
-               if (mtree->set.gid_list == NULL) {
-                       mtree->set.gid_list = new_attr_count(me, NULL);
-                       if (mtree->set.gid_list == NULL)
+               if (acs->gid_list == NULL) {
+                       acs->gid_list = attr_counter_new(me, NULL);
+                       if (acs->gid_list == NULL)
                                return (-1);
                } else {
                        last = NULL;
-                       for (ac = mtree->set.gid_list; ac; ac = ac->next) {
+                       for (ac = acs->gid_list; ac; ac = ac->next) {
                                if (ac->m_entry->gid == me->gid)
                                        break;
                                last = ac;
                        }
-                       if (inc_attr_count(
-                           &mtree->set.gid_list, ac, last, me) < 0)
+                       if (attr_counter_inc(&acs->gid_list, ac, last, me) < 0)
                                return (-1);
                }
        }
        if (keys & F_MODE) {
-               if (mtree->set.mode_list == NULL) {
-                       mtree->set.mode_list = new_attr_count(me, NULL);
-                       if (mtree->set.mode_list == NULL)
+               if (acs->mode_list == NULL) {
+                       acs->mode_list = attr_counter_new(me, NULL);
+                       if (acs->mode_list == NULL)
                                return (-1);
                } else {
                        last = NULL;
-                       for (ac = mtree->set.mode_list; ac; ac = ac->next) {
+                       for (ac = acs->mode_list; ac; ac = ac->next) {
                                if (ac->m_entry->mode == me->mode)
                                        break;
                                last = ac;
                        }
-                       if (inc_attr_count(
-                           &mtree->set.mode_list, ac, last, me) < 0)
+                       if (attr_counter_inc(&acs->mode_list, ac, last, me) < 0)
                                return (-1);
                }
        }
        if (keys & F_FLAGS) {
-               if (mtree->set.flags_list == NULL) {
-                       mtree->set.flags_list = new_attr_count(me, NULL);
-                       if (mtree->set.flags_list == NULL)
+               if (acs->flags_list == NULL) {
+                       acs->flags_list = attr_counter_new(me, NULL);
+                       if (acs->flags_list == NULL)
                                return (-1);
                } else {
                        last = NULL;
-                       for (ac = mtree->set.flags_list; ac; ac = ac->next) {
+                       for (ac = acs->flags_list; ac; ac = ac->next) {
                                if (ac->m_entry->fflags_set == me->fflags_set &&
-                                   ac->m_entry->fflags_clear == me->fflags_clear)
+                                   ac->m_entry->fflags_clear ==
+                                                       me->fflags_clear)
                                        break;
                                last = ac;
                        }
-                       if (inc_attr_count(
-                           &mtree->set.flags_list, ac, last, me) < 0)
+                       if (attr_counter_inc(&acs->flags_list, ac, last, me) < 0)
                                return (-1);
                }
        }
 
-       /*
-        * Save a entry.
-        */
-       me->next = NULL;
-       *mtree->set.me_last = me;
-       mtree->set.me_last = &me->next;
        return (0);
 }
 
+static int
+attr_counter_set_remove(struct mtree_writer *mtree, struct mtree_entry *me)
+{
+
+       /* Remove global set values for all entries. */
+       if (attr_counter_set_remove_sub(mtree, me, &mtree->all_acs) < 0)
+               return (-1);
+       if (me->dir_depth > 0) {
+               if (attr_counter_set_remove_sub(mtree, me,
+                   &me->parent->dir_info->acs) < 0)
+                       return (-1);
+       }
+       return (0);
+}
+
+static int
+attr_counter_set_remove_sub(struct mtree_writer *mtree, struct mtree_entry *me,
+    struct att_counter_set *acs)
+{
+       int keys = mtree->keys;
+       struct attr_counter *ac;
+
+       if ((keys & (F_UNAME | F_UID)) != 0 && acs->uid_list != NULL) {
+               for (ac = acs->uid_list; ac; ac = ac->next) {
+                       if (ac->m_entry->uid == me->uid) {
+                               if (attr_counter_dec(&acs->uid_list, ac) < 0)
+                                       return (-1);
+                               break;
+                       }
+               }
+       }
+       if ((keys & (F_GNAME | F_GID)) != 0 && acs->gid_list != NULL) {
+               for (ac = acs->gid_list; ac; ac = ac->next) {
+                       if (ac->m_entry->gid == me->gid) {
+                               if (attr_counter_dec(&acs->gid_list, ac) < 0)
+                                       return (-1);
+                               break;
+                       }
+               }
+       }
+       if ((keys & F_MODE) != 0 && acs->mode_list != NULL) {
+               for (ac = acs->mode_list; ac; ac = ac->next) {
+                       if (ac->m_entry->mode == me->mode) {
+                               if (attr_counter_dec(&acs->mode_list, ac) < 0)
+                                       return (-1);
+                               break;
+                       }
+               }
+       }
+       if ((keys & F_FLAGS) != 0 && acs->flags_list != NULL) {
+               for (ac = acs->flags_list; ac; ac = ac->next) {
+                       if (ac->m_entry->fflags_set == me->fflags_set &&
+                           ac->m_entry->fflags_clear == me->fflags_clear) {
+                               if (attr_counter_dec(&acs->flags_list, ac) < 0)
+                                       return (-1);
+                               break;
+                       }
+               }
+       }
+
+       return (0);
+}
+
+static void
+attr_counter_set_free(struct att_counter_set *acs)
+{
+       attr_counter_free(&acs->uid_list);
+       attr_counter_free(&acs->gid_list);
+       attr_counter_free(&acs->mode_list);
+       attr_counter_free(&acs->flags_list);
+}
+
 static int
 get_keys(struct mtree_writer *mtree, struct mtree_entry *me)
 {
@@ -788,37 +870,45 @@ get_keys(struct mtree_writer *mtree, struct mtree_entry *me)
        return (keys);
 }
 
-static struct mtree_entry *
-new_mtree_entry(struct archive_entry *entry)
+static int
+mtree_entry_new(struct archive_write *a, struct archive_entry *entry,
+    struct mtree_entry **m_entry)
 {
        struct mtree_entry *me;
        const char *s;
+       int r;
+       static const struct archive_rb_tree_ops rb_ops = {
+               mtree_entry_cmp_node, mtree_entry_cmp_key
+       };
 
        me = calloc(1, sizeof(*me));
-       if (me == NULL)
-               return (NULL);
-       me->pathname = strdup(archive_entry_pathname(entry));
+       if (me == NULL) {
+               archive_set_error(&a->archive, ENOMEM,
+                   "Can't allocate mtree entry");
+               *m_entry = NULL;
+               return (ARCHIVE_FATAL);
+       }
+
+       r = mtree_entry_setup_filenames(a, me, entry);
+       if (r < ARCHIVE_WARN) {
+               mtree_entry_free(me);
+               *m_entry = NULL;
+               return (r);
+       }
+
        if ((s = archive_entry_symlink(entry)) != NULL)
-               me->symlink = strdup(s);
-       else
-               me->symlink = NULL;
+               archive_strcpy(&me->symlink, s);
        me->nlink = archive_entry_nlink(entry);
        me->filetype = archive_entry_filetype(entry);
        me->mode = archive_entry_mode(entry) & 07777;
        me->uid = archive_entry_uid(entry);
        me->gid = archive_entry_gid(entry);
        if ((s = archive_entry_uname(entry)) != NULL)
-               me->uname = strdup(s);
-       else
-               me->uname = NULL;
+               archive_strcpy(&me->uname, s);
        if ((s = archive_entry_gname(entry)) != NULL)
-               me->gname = strdup(s);
-       else
-               me->gname = NULL;
+               archive_strcpy(&me->gname, s);
        if ((s = archive_entry_fflags_text(entry)) != NULL)
-               me->fflags_text = strdup(s);
-       else
-               me->fflags_text = NULL;
+               archive_strcpy(&me->fflags_text, s);
        archive_entry_fflags(entry, &me->fflags_set, &me->fflags_clear);
        me->mtime = archive_entry_mtime(entry);
        me->mtime_nsec = archive_entry_mtime_nsec(entry);
@@ -826,18 +916,39 @@ new_mtree_entry(struct archive_entry *entry)
        me->rdevminor = archive_entry_rdevminor(entry);
        me->size = archive_entry_size(entry);
        me->compute_sum = 0;
+       if (me->filetype == AE_IFDIR) {
+               me->dir_info = malloc(sizeof(*me->dir_info));
+               if (me->dir_info == NULL) {
+                       mtree_entry_free(me);
+                       archive_set_error(&a->archive, ENOMEM,
+                           "Can't allocate mtree entry");
+                       *m_entry = NULL;
+                       return (ARCHIVE_FATAL);
+               }
+               __archive_rb_tree_init(&me->dir_info->rbtree, &rb_ops);
+               me->dir_info->children.first = NULL;
+               me->dir_info->children.last = &(me->dir_info->children.first);
+               memset(&me->dir_info->acs, 0, sizeof(me->dir_info->acs));
+       }
 
-       return (me);
+       *m_entry = me;
+       return (ARCHIVE_OK);
 }
 
 static void
-free_mtree_entry(struct mtree_entry *me)
+mtree_entry_free(struct mtree_entry *me)
 {
-       free(me->pathname);
-       free(me->symlink);
-       free(me->uname);
-       free(me->gname);
-       free(me->fflags_text);
+       archive_string_free(&me->parentdir);
+       archive_string_free(&me->basename);
+       archive_string_free(&me->pathname);
+       archive_string_free(&me->symlink);
+       archive_string_free(&me->uname);
+       archive_string_free(&me->gname);
+       archive_string_free(&me->fflags_text);
+       if (me->dir_info) {
+               attr_counter_set_free(&me->dir_info->acs);
+               free(me->dir_info);
+       }
        free(me);
 }
 
@@ -846,23 +957,28 @@ archive_write_mtree_header(struct archive_write *a,
     struct archive_entry *entry)
 {
        struct mtree_writer *mtree= a->format_data;
+       struct mtree_entry *mtree_entry;
+       int r, r2;
 
        if (mtree->first) {
                mtree->first = 0;
                archive_strcat(&mtree->buf, "#mtree\n");
                if ((mtree->keys & SET_KEYS) == 0)
-                       mtree->set.output = 0;/* Disalbed. */
+                       mtree->output_global_set = 0;/* Disalbed. */
        }
 
        mtree->entry_bytes_remaining = archive_entry_size(entry);
        if (mtree->dironly && archive_entry_filetype(entry) != AE_IFDIR)
                return (ARCHIVE_OK);
 
-       mtree->mtree_entry = new_mtree_entry(entry);
-       if (mtree->mtree_entry == NULL) {
-               archive_set_error(&a->archive, ENOMEM,
-                   "Can't allocate mtree entry");
-               return (ARCHIVE_FATAL);
+       mtree->mtree_entry = NULL;
+       r2 = mtree_entry_new(a, entry, &mtree_entry);
+       if (r2 < ARCHIVE_WARN)
+               return (r2);
+       r = mtree_entry_tree(a, mtree_entry);
+       if (r < ARCHIVE_WARN) {
+               mtree_entry_free(mtree_entry);
+               return (r);
        }
 
        mtree->compute_sum = 0;
@@ -875,50 +991,68 @@ archive_write_mtree_header(struct archive_write *a,
        /* Initialize a bunch of sum check context. */
        sum_init(mtree);
 
-       return (ARCHIVE_OK);
+       return (r2);
 }
 
 static int
-write_entry(struct archive_write *a, struct mtree_entry *me)
+write_mtree_entry(struct archive_write *a, struct mtree_entry *me)
 {
        struct mtree_writer *mtree = a->format_data;
        struct archive_string *str;
        int keys, ret;
 
+       if (me->dir_info) {
+               if (mtree->classic) {
+                       /*
+                        * Output a comment line to describe the full
+                        * pathname of the entry as mtree utility does
+                        * while generating classic format.
+                        */
+                       if (!mtree->dironly)
+                               archive_strappend_char(&mtree->buf, '\n');
+                       if (me->parentdir.s)
+                               archive_string_sprintf(&mtree->buf,
+                                   "# %s/%s\n",
+                                   me->parentdir.s, me->basename.s);
+                       else
+                               archive_string_sprintf(&mtree->buf,
+                                   "# %s\n",
+                                   me->basename.s);
+               }
+               if (mtree->output_global_set)
+                       write_global(mtree, &me->dir_info->acs);
+       }
        archive_string_empty(&mtree->ebuf);
-       str = (mtree->indent)? &mtree->ebuf : &mtree->buf;
+       str = (mtree->indent || mtree->classic)? &mtree->ebuf : &mtree->buf;
+
+       if (!mtree->classic && me->parentdir.s) {
+               /*
+                * If generating format is not classic one(v1), output
+                * a full pathname.
+                */
+               mtree_quote(str, me->parentdir.s);
+               archive_strappend_char(str, '/');
+       }
+       mtree_quote(str, me->basename.s);
 
-       /* If the pathname does not have a path separator, we have to
-        * add "./" to the head of the pathename because mtree reader
-        * will suppose that it is v1(a.k.a classic) mtree format and
-        * change the directory unexpectedly and so it will make a wrong
-        * path. */
-       if (strchr(me->pathname, '/') == NULL &&
-           strcmp(me->pathname, ".") != 0) {
-               archive_strcat(str, "./");
-       }
-       /* mtree reader does not accept an absolute path. */
-       else if (me->pathname[0] == '/')
-               archive_strappend_char(str, '.');
-       mtree_quote(str, me->pathname);
        keys = get_keys(mtree, me);
        if ((keys & F_NLINK) != 0 &&
            me->nlink != 1 && me->filetype != AE_IFDIR)
                archive_string_sprintf(str, " nlink=%u", me->nlink);
 
-       if ((keys & F_GNAME) != 0 && me->gname != NULL) {
+       if ((keys & F_GNAME) != 0 && archive_strlen(&me->gname) > 0) {
                archive_strcat(str, " gname=");
-               mtree_quote(str, me->gname);
+               mtree_quote(str, me->gname.s);
        }
-       if ((keys & F_UNAME) != 0 && me->uname != NULL) {
+       if ((keys & F_UNAME) != 0 && archive_strlen(&me->uname) > 0) {
                archive_strcat(str, " uname=");
-               mtree_quote(str, me->uname);
+               mtree_quote(str, me->uname.s);
        }
        if ((keys & F_FLAGS) != 0) {
-               if (me->fflags_text != NULL) {
+               if (archive_strlen(&me->fflags_text) > 0) {
                        archive_strcat(str, " flags=");
-                       mtree_quote(str, me->fflags_text);
-               } else if (mtree->set.processed &&
+                       mtree_quote(str, me->fflags_text.s);
+               } else if (mtree->set.processing &&
                    (mtree->set.keys & F_FLAGS) != 0)
                        /* Overwrite the global parameter. */
                        archive_strcat(str, " flags=none");
@@ -939,7 +1073,7 @@ write_entry(struct archive_write *a, struct mtree_entry *me)
                        archive_strcat(str, " type=link");
                if ((keys & F_SLINK) != 0) {
                        archive_strcat(str, " link=");
-                       mtree_quote(str, me->symlink);
+                       mtree_quote(str, me->symlink.s);
                }
                break;
        case AE_IFSOCK:
@@ -988,12 +1122,45 @@ write_entry(struct archive_write *a, struct mtree_entry *me)
        if (me->filetype == AE_IFREG)
                sum_write(str, me);
 
-       archive_strcat(str, "\n");
-       if (mtree->indent)
+       archive_strappend_char(str, '\n');
+       if (mtree->indent || mtree->classic)
+               mtree_indent(mtree);
+
+       if (mtree->buf.length > 32768) {
+               ret = __archive_write_output(
+                       a, mtree->buf.s, mtree->buf.length);
+               archive_string_empty(&mtree->buf);
+       } else
+               ret = ARCHIVE_OK;
+       return (ret);
+}
+
+static int
+write_dot_dot_entry(struct archive_write *a, struct mtree_entry *n)
+{
+       struct mtree_writer *mtree = a->format_data;
+       int ret;
+
+       if (n->parentdir.s) {
+               if (mtree->indent) {
+                       int i, pd = mtree->depth * 4;
+                       for (i = 0; i < pd; i++)
+                               archive_strappend_char(&mtree->buf, ' ');
+               }
+               archive_string_sprintf(&mtree->buf, "# %s/%s\n",
+                       n->parentdir.s, n->basename.s);
+       }
+
+       if (mtree->indent) {
+               archive_string_empty(&mtree->ebuf);
+               archive_strncat(&mtree->ebuf, "..\n\n", (mtree->dironly)?3:4);
                mtree_indent(mtree);
+       } else
+               archive_strncat(&mtree->buf, "..\n\n", (mtree->dironly)?3:4);
 
        if (mtree->buf.length > 32768) {
-               ret = __archive_write_output(a, mtree->buf.s, mtree->buf.length);
+               ret = __archive_write_output(
+                       a, mtree->buf.s, mtree->buf.length);
                archive_string_empty(&mtree->buf);
        } else
                ret = ARCHIVE_OK;
@@ -1001,29 +1168,82 @@ write_entry(struct archive_write *a, struct mtree_entry *me)
 }
 
 /*
- * Write mtree entries saved at collect_set_values() function.
+ * Write mtree entries saved at attr_counter_set_collect() function.
  */
 static int
-write_mtree_entries(struct archive_write *a)
+write_mtree_entry_tree(struct archive_write *a)
 {
        struct mtree_writer *mtree = a->format_data;
-       struct mtree_entry *me, *tme;
+       struct mtree_entry *np = mtree->root;
+       struct archive_rb_node *n;
        int ret;
 
-       for (me = mtree->set.me_first; me; me = me->next) {
-               ret = write_entry(a, me);
-               if (ret != ARCHIVE_OK)
-                       return (ARCHIVE_FATAL);
-       }
+       do {
+               if (!np->virtual_dir || mtree->classic) {
+                       ret = write_mtree_entry(a, np);
+                       if (ret != ARCHIVE_OK)
+                               return (ARCHIVE_FATAL);
+               }
+               /*
+                * Output the attribute of all files except directory files.
+                */
+               mtree->depth++;
+               ARCHIVE_RB_TREE_FOREACH(n, &(np->dir_info->rbtree)) {
+                       struct mtree_entry *e = (struct mtree_entry *)n;
+                       if (e->dir_info)
+                               mtree_entry_add_child_tail(np, e);
+                       else {
+                               ret = write_mtree_entry(a, e);
+                               if (ret != ARCHIVE_OK)
+                                       return (ARCHIVE_FATAL);
+                       }
+               }
+               mtree->depth--;
+
+               if (np->dir_info->children.first != NULL) {
+                       /*
+                        * Descend the tree.
+                        */
+                       np = np->dir_info->children.first;
+                       if (mtree->indent)
+                               mtree->depth++;
+                       continue;
+               } else if (mtree->classic) {
+                       /*
+                        * While printing mtree classic, if there are not
+                        * any directory files(except "." and "..") in the
+                        * directory, output two dots ".." as returning
+                        * the parent directory.
+                        */
+                       ret = write_dot_dot_entry(a, np);
+                       if (ret != ARCHIVE_OK)
+                               return (ARCHIVE_FATAL);
+               }
+
+               while (np != np->parent) {
+                       if (np->chnext == NULL) {
+                               /*
+                                * Ascend the tree; go back to the parent.
+                                */
+                               if (mtree->indent)
+                                       mtree->depth--;
+                               if (mtree->classic) {
+                                       ret = write_dot_dot_entry(a,
+                                               np->parent);
+                                       if (ret != ARCHIVE_OK)
+                                               return (ARCHIVE_FATAL);
+                               }
+                               np = np->parent;
+                       } else {
+                               /*
+                                * Switch to next mtree entry in the directory.
+                                */
+                               np = np->chnext;
+                               break;
+                       }
+               }
+       } while (np != np->parent); 
 
-       me = mtree->set.me_first;
-       while (me != NULL) {
-               tme = me->next;
-               free_mtree_entry(me);
-               me = tme;
-       }
-       mtree->set.me_first = NULL;
-       mtree->set.me_last = &mtree->set.me_first;
        return (ARCHIVE_OK);
 }
 
@@ -1032,7 +1252,6 @@ archive_write_mtree_finish_entry(struct archive_write *a)
 {
        struct mtree_writer *mtree = a->format_data;
        struct mtree_entry *me;
-       int ret;
 
        if ((me = mtree->mtree_entry) == NULL)
                return (ARCHIVE_OK);
@@ -1041,31 +1260,7 @@ archive_write_mtree_finish_entry(struct archive_write *a)
        if (me->filetype == AE_IFREG)
                sum_final(mtree, me);
 
-       if (mtree->set.output) {
-               if (!mtree->dironly) {
-                       if (archive_strlen(&mtree->set.parent) == 0)
-                               parent_dir_changed(&mtree->set.parent, me);
-                       if (parent_dir_changed(&mtree->set.parent, me)) {
-                               /* Write /set keyword */
-                               write_global(mtree);
-                               /* Write entries saved by
-                                * collect_set_values() function. */
-                               ret = write_mtree_entries(a);
-                               if (ret != ARCHIVE_OK)
-                                       return (ARCHIVE_FATAL);
-                       }
-               }
-               /* Tabulate uid,gid,mode and fflags of a entry
-                * in order to be used for /set. and, at this time
-                * we do not write a entry.  */
-               collect_set_values(mtree, me);
-               return (ARCHIVE_OK);
-       } else {
-               /* Write the current entry and free it. */
-               ret = write_entry(a, me);
-               free_mtree_entry(me);
-       }
-       return (ret == ARCHIVE_OK ? ret : ARCHIVE_FATAL);
+       return (ARCHIVE_OK);
 }
 
 static int
@@ -1074,9 +1269,10 @@ archive_write_mtree_close(struct archive_write *a)
        struct mtree_writer *mtree= a->format_data;
        int ret;
 
-       if (mtree->set.output && mtree->set.me_first != NULL) {
-               write_global(mtree);
-               ret = write_mtree_entries(a);
+       if (mtree->root != NULL) {
+               if (mtree->output_global_set)
+                       write_global(mtree, &mtree->all_acs);
+               ret = write_mtree_entry_tree(a);
                if (ret != ARCHIVE_OK)
                        return (ARCHIVE_FATAL);
        }
@@ -1109,25 +1305,16 @@ static int
 archive_write_mtree_free(struct archive_write *a)
 {
        struct mtree_writer *mtree= a->format_data;
-       struct mtree_entry *me, *tme;
 
        if (mtree == NULL)
                return (ARCHIVE_OK);
 
        /* Make sure we dot not leave any entries. */
-       me = mtree->set.me_first;
-       while (me != NULL) {
-               tme = me->next;
-               free_mtree_entry(me);
-               me = tme;
-       }
+       mtree_entry_register_free(mtree);
+       archive_string_free(&mtree->cur_dirstr);
        archive_string_free(&mtree->ebuf);
        archive_string_free(&mtree->buf);
-       archive_string_free(&mtree->set.parent);
-       free_attr_count(&mtree->set.uid_list);
-       free_attr_count(&mtree->set.gid_list);
-       free_attr_count(&mtree->set.mode_list);
-       free_attr_count(&mtree->set.flags_list);
+       attr_counter_set_free(&mtree->all_acs);
        free(mtree);
        a->format_data = NULL;
        return (ARCHIVE_OK);
@@ -1222,7 +1409,7 @@ archive_write_mtree_options(struct archive_write *a, const char *key,
                else if (strcmp(key, "uname") == 0)
                        keybit = F_UNAME;
                else if (strcmp(key, "use-set") == 0) {
-                       mtree->set.output = (value != NULL)? 1: 0;
+                       mtree->output_global_set = (value != NULL)? 1: 0;
                        return (ARCHIVE_OK);
                }
                break;
@@ -1241,14 +1428,13 @@ archive_write_mtree_options(struct archive_write *a, const char *key,
        return (ARCHIVE_WARN);
 }
 
-int
-archive_write_set_format_mtree(struct archive *_a)
+static int
+archive_write_set_format_mtree_default(struct archive *_a, const char *fn)
 {
        struct archive_write *a = (struct archive_write *)_a;
        struct mtree_writer *mtree;
 
-       archive_check_magic(_a, ARCHIVE_WRITE_MAGIC,
-           ARCHIVE_STATE_NEW, "archive_write_set_format_mtree");
+       archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_NEW, fn);
 
        if (a->format_free != NULL)
                (a->format_free)(a);
@@ -1262,14 +1448,12 @@ archive_write_set_format_mtree(struct archive *_a)
        mtree->mtree_entry = NULL;
        mtree->first = 1;
        memset(&(mtree->set), 0, sizeof(mtree->set));
-       archive_string_init(&mtree->set.parent);
        mtree->keys = DEFAULT_KEYS;
        mtree->dironly = 0;
        mtree->indent = 0;
        archive_string_init(&mtree->ebuf);
        archive_string_init(&mtree->buf);
-       mtree->set.me_first = NULL;
-       mtree->set.me_last = &mtree->set.me_first;
+       mtree_entry_register_init(mtree);
        a->format_data = mtree;
        a->format_free = archive_write_mtree_free;
        a->format_name = "mtree";
@@ -1284,6 +1468,35 @@ archive_write_set_format_mtree(struct archive *_a)
        return (ARCHIVE_OK);
 }
 
+int
+archive_write_set_format_mtree(struct archive *_a)
+{
+       return archive_write_set_format_mtree_default(_a,
+               "archive_write_set_format_mtree");
+}
+
+int
+archive_write_set_format_mtree_classic(struct archive *_a)
+{
+       int r;
+
+       r = archive_write_set_format_mtree_default(_a,
+               "archive_write_set_format_mtree_classic");
+       if (r == ARCHIVE_OK) {
+               struct archive_write *a = (struct archive_write *)_a;
+               struct mtree_writer *mtree;
+
+               mtree = (struct mtree_writer *)a->format_data;
+
+               /* Set to output a mtree archive in classic format. */
+               mtree->classic = 1;
+               /* Basically, mtree classic format uses '/set' global
+                * value. */
+               mtree->output_global_set = 1;
+       }
+       return (r);
+}
+
 static void
 sum_init(struct mtree_writer *mtree)
 {
@@ -1482,3 +1695,580 @@ sum_write(struct archive_string *str, struct mtree_entry *me)
        }
 #endif
 }
+
+static int
+mtree_entry_cmp_node(const struct archive_rb_node *n1,
+    const struct archive_rb_node *n2)
+{
+       const struct mtree_entry *e1 = (const struct mtree_entry *)n1;
+       const struct mtree_entry *e2 = (const struct mtree_entry *)n2;
+
+       return (strcmp(e2->basename.s, e1->basename.s));
+}
+
+static int
+mtree_entry_cmp_key(const struct archive_rb_node *n, const void *key)
+{
+       const struct mtree_entry *e = (const struct mtree_entry *)n;
+
+       return (strcmp((const char *)key, e->basename.s));
+}
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+static int
+cleanup_backslash_1(char *p)
+{
+       int mb, dos;
+
+       mb = dos = 0;
+       while (*p) {
+               if (*(unsigned char *)p > 127)
+                       mb = 1;
+               if (*p == '\\') {
+                       /* If we have not met any multi-byte characters,
+                        * we can replace '\' with '/'. */
+                       if (!mb)
+                               *p = '/';
+                       dos = 1;
+               }
+               p++;
+       }
+       if (!mb || !dos)
+               return (0);
+       return (-1);
+}
+
+static void
+cleanup_backslash_2(wchar_t *p)
+{
+
+       /* Convert a path-separator from '\' to  '/' */
+       while (*p != L'\0') {
+               if (*p == L'\\')
+                       *p = L'/';
+               p++;
+       }
+}
+#endif
+
+/*
+ * Generate a parent directory name and a base name from a pathname.
+ */
+static int
+mtree_entry_setup_filenames(struct archive_write *a, struct mtree_entry *file,
+    struct archive_entry *entry)
+{
+       struct mtree_writer *mtree;
+       const char *pathname;
+       char *p, *dirname, *slash;
+       size_t len;
+       int ret = ARCHIVE_OK;
+
+       mtree = (struct mtree_writer *)a->format_data;
+
+       archive_strcpy(&file->pathname, archive_entry_pathname(entry));
+#if defined(_WIN32) || defined(__CYGWIN__)
+       /*
+        * Convert a path-separator from '\' to  '/'
+        */
+       if (cleanup_backslash_1(file->pathname.s) != 0) {
+               const wchar_t *wp = archive_entry_pathname_w(entry);
+               struct archive_wstring ws;
+
+               if (wp != NULL) {
+                       int r;
+                       archive_string_init(&ws);
+                       archive_wstrcpy(&ws, wp);
+                       cleanup_backslash_2(ws.s);
+                       archive_string_empty(&(file->pathname));
+                       r = archive_string_append_from_wcs(&(file->pathname),
+                           ws.s, ws.length);
+                       archive_wstring_free(&ws);
+                       if (r < 0 && errno == ENOMEM) {
+                               archive_set_error(&a->archive, ENOMEM,
+                                   "Can't allocate memory");
+                               return (ARCHIVE_FATAL);
+                       }
+               }
+       }
+#endif
+       pathname =  file->pathname.s;
+       if (strcmp(pathname, ".") == 0) {
+               archive_strcpy(&file->basename, ".");
+               return (ARCHIVE_OK);
+       }
+
+       archive_strcpy(&(file->parentdir), pathname);
+
+       len = file->parentdir.length;
+       p = dirname = file->parentdir.s;
+
+       /*
+        * Remove leading '/' and '../' elements
+        */
+       while (*p) {
+               if (p[0] == '/') {
+                       p++;
+                       len--;
+               } else if (p[0] != '.')
+                       break;
+               else if (p[1] == '.' && p[2] == '/') {
+                       p += 3;
+                       len -= 3;
+               } else
+                       break;
+       }
+       if (p != dirname) {
+               memmove(dirname, p, len+1);
+               p = dirname;
+       }
+       /*
+        * Remove "/","/." and "/.." elements from tail.
+        */
+       while (len > 0) {
+               size_t ll = len;
+
+               if (len > 0 && p[len-1] == '/') {
+                       p[len-1] = '\0';
+                       len--;
+               }
+               if (len > 1 && p[len-2] == '/' && p[len-1] == '.') {
+                       p[len-2] = '\0';
+                       len -= 2;
+               }
+               if (len > 2 && p[len-3] == '/' && p[len-2] == '.' &&
+                   p[len-1] == '.') {
+                       p[len-3] = '\0';
+                       len -= 3;
+               }
+               if (ll == len)
+                       break;
+       }
+       while (*p) {
+               if (p[0] == '/') {
+                       if (p[1] == '/')
+                               /* Convert '//' --> '/' */
+                               strcpy(p, p+1);
+                       else if (p[1] == '.' && p[2] == '/')
+                               /* Convert '/./' --> '/' */
+                               strcpy(p, p+2);
+                       else if (p[1] == '.' && p[2] == '.' && p[3] == '/') {
+                               /* Convert 'dir/dir1/../dir2/'
+                                *     --> 'dir/dir2/'
+                                */
+                               char *rp = p -1;
+                               while (rp >= dirname) {
+                                       if (*rp == '/')
+                                               break;
+                                       --rp;
+                               }
+                               if (rp > dirname) {
+                                       strcpy(rp, p+3);
+                                       p = rp;
+                               } else {
+                                       strcpy(dirname, p+4);
+                                       p = dirname;
+                               }
+                       } else
+                               p++;
+               } else
+                       p++;
+       }
+       p = dirname;
+       len = strlen(p);
+
+       /*
+        * Add "./" prefiex.
+        * NOTE: If the pathname does not have a path separator, we have
+        * to add "./" to the head of the pathename because mtree reader
+        * will suppose that it is v1(a.k.a classic) mtree format and
+        * change the directory unexpectedly and so it will make a wrong
+        * path.
+        */
+       if (strcmp(p, ".") != 0 && strncmp(p, "./", 2) != 0) {
+               struct archive_string as;
+               archive_string_init(&as);
+               archive_strcpy(&as, "./");
+               archive_strncat(&as, p, len);
+               archive_string_empty(&file->parentdir);
+               archive_string_concat(&file->parentdir, &as);
+               archive_string_free(&as);
+               p = file->parentdir.s;
+               len = archive_strlen(&file->parentdir);
+       }
+
+       /*
+        * - Count up directory elements.
+        * - Find out the position which points the last position of
+        *   path separator('/').
+        */
+       slash = NULL;
+       file->dir_depth = 0;
+       for (; *p != '\0'; p++)
+               if (*p == '/') {
+                       slash = p;
+                       file->dir_depth++;
+               }
+       if (slash == NULL) {
+               /* The pathname doesn't have a parent directory. */
+               file->parentdir.length = len;
+               archive_string_copy(&(file->basename), &(file->parentdir));
+               archive_string_empty(&(file->parentdir));
+               *file->parentdir.s = '\0';
+               return (ret);
+       }
+
+       /* Make a basename from dirname and slash */
+       *slash  = '\0';
+       file->parentdir.length = slash - dirname;
+       archive_strcpy(&(file->basename),  slash + 1);
+       if (file->dir_info)
+               file->dir_depth ++;
+       return (ret);
+}
+
+static int
+mtree_entry_create_virtual_dir(struct archive_write *a, const char *pathname,
+    struct mtree_entry **m_entry)
+{
+       struct archive_entry *entry;
+       struct mtree_entry *file;
+       int r;
+
+       entry = archive_entry_new();
+       if (entry == NULL) {
+               *m_entry = NULL;
+               archive_set_error(&a->archive, ENOMEM,
+                   "Can't allocate memory");
+               return (ARCHIVE_FATAL);
+       }
+       archive_entry_copy_pathname(entry, pathname);
+       archive_entry_set_mode(entry, AE_IFDIR | 0755);
+
+       r = mtree_entry_new(a, entry, &file);
+       archive_entry_free(entry);
+       if (r < ARCHIVE_WARN) {
+               *m_entry = NULL;
+               archive_set_error(&a->archive, ENOMEM,
+                   "Can't allocate memory");
+               return (ARCHIVE_FATAL);
+       }
+
+       file->virtual_dir = 1;
+
+       *m_entry = file;
+       return (ARCHIVE_OK);
+}
+
+static void
+mtree_entry_register_add(struct mtree_writer *mtree, struct mtree_entry *file)
+{
+        file->next = NULL;
+        *mtree->file_list.last = file;
+        mtree->file_list.last = &(file->next);
+}
+
+static void
+mtree_entry_register_init(struct mtree_writer *mtree)
+{
+       mtree->file_list.first = NULL;
+       mtree->file_list.last = &(mtree->file_list.first);
+}
+
+static void
+mtree_entry_register_free(struct mtree_writer *mtree)
+{
+       struct mtree_entry *file, *file_next;
+
+       file = mtree->file_list.first;
+       while (file != NULL) {
+               file_next = file->next;
+               mtree_entry_free(file);
+               file = file_next;
+       }
+}
+
+static int
+mtree_entry_add_child_tail(struct mtree_entry *parent,
+    struct mtree_entry *child)
+{
+       child->chnext = NULL;
+       *parent->dir_info->children.last = child;
+       parent->dir_info->children.last = &(child->chnext);
+       return (1);
+}
+
+/*
+ * Find a entry from a parent entry with the name.
+ */
+static struct mtree_entry *
+mtree_entry_find_child(struct mtree_entry *parent, const char *child_name)
+{
+       struct mtree_entry *np;
+
+       if (parent == NULL)
+               return (NULL);
+       np = (struct mtree_entry *)__archive_rb_tree_find_node(
+           &(parent->dir_info->rbtree), child_name);
+       return (np);
+}
+
+static int
+get_path_component(char *name, int n, const char *fn)
+{
+       char *p;
+       int l;
+
+       p = strchr(fn, '/');
+       if (p == NULL) {
+               if ((l = strlen(fn)) == 0)
+                       return (0);
+       } else
+               l = p - fn;
+       if (l > n -1)
+               return (-1);
+       memcpy(name, fn, l);
+       name[l] = '\0';
+
+       return (l);
+}
+
+/*
+ * Add a new entry into the tree.
+ */
+static int
+mtree_entry_tree(struct archive_write *a, struct mtree_entry *file)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       char name[_MAX_FNAME];/* Included null terminator size. */
+#elif defined(NAME_MAX) && NAME_MAX >= 255
+       char name[NAME_MAX+1];
+#else
+       char name[256];
+#endif
+       struct mtree_writer *mtree = (struct mtree_writer *)a->format_data;
+       struct mtree_entry *dent, *np;
+       const char *fn, *p;
+       int l, r;
+
+       if (strcmp(file->pathname.s, ".") == 0) {
+               file->parent = file;
+               if (mtree->root != NULL) {
+                       np = mtree->root;
+                       goto same_entry;
+               }
+               mtree->root = file;
+               mtree_entry_register_add(mtree, file);
+               if (mtree->output_global_set)
+                       attr_counter_set_collect(mtree, file);
+               mtree->mtree_entry = file;
+               return (ARCHIVE_OK);
+       }
+
+       if (file->parentdir.length > 0)
+               fn = p = file->parentdir.s;
+       else
+               fn = p = "";
+
+       /*
+        * If the path of the parent directory of `file' entry is
+        * the same as the path of `cur_dirent', add `file' entry to
+        * `cur_dirent'.
+        */
+       if (archive_strlen(&(mtree->cur_dirstr))
+             == archive_strlen(&(file->parentdir)) &&
+           strcmp(mtree->cur_dirstr.s, fn) == 0) {
+               if (!__archive_rb_tree_insert_node(
+                   &(mtree->cur_dirent->dir_info->rbtree),
+                   (struct archive_rb_node *)file)) {
+                       np = (struct mtree_entry *)__archive_rb_tree_find_node(
+                           &(mtree->cur_dirent->dir_info->rbtree),
+                           file->basename.s);
+                       goto same_entry;
+               }
+               file->parent = mtree->cur_dirent;
+               mtree_entry_register_add(mtree, file);
+               if (mtree->output_global_set)
+                       attr_counter_set_collect(mtree, file);
+               mtree->mtree_entry = file;
+               return (ARCHIVE_OK);
+       }
+
+       dent = mtree->root;
+       for (;;) {
+               l = get_path_component(name, sizeof(name), fn);
+               if (l == 0) {
+                       np = NULL;
+                       break;
+               }
+               if (l < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "A name buffer is too small");
+                       return (ARCHIVE_FATAL);
+               }
+               if (l == 1 && name[0] == '.' && dent != NULL &&
+                   dent == mtree->root) {
+                       fn += l;
+                       if (fn[0] == '/')
+                               fn++;
+                       continue;
+               }
+
+               np = mtree_entry_find_child(dent, name);
+               if (np == NULL || fn[0] == '\0')
+                       break;
+
+               /* Find next subdirectory. */
+               if (!np->dir_info) {
+                       /* NOT Directory! */
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "`%s' is not directory, we cannot insert `%s' ",
+                           np->pathname.s, file->pathname.s);
+                       return (ARCHIVE_FAILED);
+               }
+               fn += l;
+               if (fn[0] == '/')
+                       fn++;
+               dent = np;
+       }
+       if (np == NULL) {
+               /*
+                * Create virtual parent directories.
+                */
+               while (fn[0] != '\0') {
+                       struct mtree_entry *vp;
+                       struct archive_string as;
+
+                       archive_string_init(&as);
+                       archive_strncat(&as, p, fn - p + l);
+                       if (as.s[as.length-1] == '/') {
+                               as.s[as.length-1] = '\0';
+                               as.length--;
+                       }
+                       r = mtree_entry_create_virtual_dir(a, as.s, &vp);
+                       archive_string_free(&as);
+                       if (r < ARCHIVE_WARN)
+                               return (r);
+
+                       if (strcmp(vp->pathname.s, ".") == 0) {
+                               vp->parent = vp;
+                               mtree->root = vp;
+                       } else {
+                               __archive_rb_tree_insert_node(
+                                   &(dent->dir_info->rbtree),
+                                   (struct archive_rb_node *)vp);
+                               vp->parent = dent;
+                       }
+                       mtree_entry_register_add(mtree, vp);
+                       if (mtree->output_global_set)
+                               attr_counter_set_collect(mtree, vp);
+                       np = vp;
+
+                       fn += l;
+                       if (fn[0] == '/')
+                               fn++;
+                       l = get_path_component(name, sizeof(name), fn);
+                       if (l < 0) {
+                               archive_string_free(&as);
+                               archive_set_error(&a->archive,
+                                   ARCHIVE_ERRNO_MISC,
+                                   "A name buffer is too small");
+                               return (ARCHIVE_FATAL);
+                       }
+                       dent = np;
+               }
+
+               /* Found out the parent directory where `file' can be
+                * inserted. */
+               mtree->cur_dirent = dent;
+               archive_string_empty(&(mtree->cur_dirstr));
+               archive_string_ensure(&(mtree->cur_dirstr),
+                   archive_strlen(&(dent->parentdir)) +
+                   archive_strlen(&(dent->basename)) + 2);
+               if (archive_strlen(&(dent->parentdir)) +
+                   archive_strlen(&(dent->basename)) == 0)
+                       mtree->cur_dirstr.s[0] = 0;
+               else {
+                       if (archive_strlen(&(dent->parentdir)) > 0) {
+                               archive_string_copy(&(mtree->cur_dirstr),
+                                   &(dent->parentdir));
+                               archive_strappend_char(
+                                   &(mtree->cur_dirstr), '/');
+                       }
+                       archive_string_concat(&(mtree->cur_dirstr),
+                           &(dent->basename));
+               }
+
+               if (!__archive_rb_tree_insert_node(
+                   &(dent->dir_info->rbtree),
+                   (struct archive_rb_node *)file)) {
+                       np = (struct mtree_entry *)__archive_rb_tree_find_node(
+                           &(dent->dir_info->rbtree), file->basename.s);
+                       goto same_entry;
+               }
+               file->parent = dent;
+               mtree_entry_register_add(mtree, file);
+               if (mtree->output_global_set)
+                       attr_counter_set_collect(mtree, file);
+               mtree->mtree_entry = file;
+               return (ARCHIVE_OK);
+       }
+
+same_entry:
+       /*
+        * We have already has the entry the filename of which is
+        * the same.
+        */
+       r = mtree_entry_exchange_same_entry(a, np, file);
+       if (r < ARCHIVE_WARN)
+               return (r);
+       np->virtual_dir = 0;
+       mtree->mtree_entry = np;
+       mtree_entry_free(file);
+       return (ARCHIVE_WARN);
+}
+
+static int
+mtree_entry_exchange_same_entry(struct archive_write *a, struct mtree_entry *np,
+    struct mtree_entry *file)
+{
+       struct mtree_writer *mtree = (struct mtree_writer *)a->format_data;
+
+       if ((np->mode & AE_IFMT) != (file->mode & AE_IFMT)) {
+               archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                   "Found duplicate entries `%s' and its file type is "
+                   "different",
+                   np->pathname.s);
+               return (ARCHIVE_FAILED);
+       }
+
+       /* Update the existent mtree entry's attributes by the new one's. */
+       if (mtree->output_global_set) {
+               attr_counter_set_remove(mtree, np);
+               attr_counter_set_collect(mtree, file);
+       }
+       archive_string_empty(&np->symlink);
+       archive_string_concat(&np->symlink, &file->symlink);
+       archive_string_empty(&np->uname);
+       archive_string_concat(&np->uname, &file->uname);
+       archive_string_empty(&np->gname);
+       archive_string_concat(&np->gname, &file->gname);
+       archive_string_empty(&np->fflags_text);
+       archive_string_concat(&np->fflags_text, &file->fflags_text);
+       np->nlink = file->nlink;
+       np->filetype = file->filetype;
+       np->mode = file->mode;
+       np->size = file->size;
+       np->uid = file->uid;
+       np->gid = file->gid;
+       np->fflags_set = file->fflags_set;
+       np->fflags_clear = file->fflags_clear;
+       np->mtime = file->mtime;
+       np->mtime_nsec = file->mtime_nsec;
+       np->rdevmajor = file->rdevmajor;
+       np->rdevminor = file->rdevminor;
+
+       return (ARCHIVE_WARN);
+}
index ee436d08d5bf0b88bcdd8f7e619f165e91d72991..c886709bf19c298c3db6b13b65502f4f0548c4e9 100644 (file)
@@ -35,11 +35,11 @@ static struct {
        uid_t            uid;
        gid_t            gid;
 } entries[] = {
+       { "./COPYING",          S_IFREG | 0644, 1231975636, 1001, 1001 },
        { "./Makefile",         S_IFREG | 0644, 1233041050, 1001, 1001 },
        { "./NEWS",             S_IFREG | 0644, 1231975636, 1001, 1001 },
        { "./PROJECTS",         S_IFREG | 0644, 1231975636, 1001, 1001 },
        { "./README",           S_IFREG | 0644, 1231975636, 1001, 1001 },
-       { "./COPYING",          S_IFREG | 0644, 1231975636, 1001, 1001 },
        { "./subdir",           S_IFDIR | 0755, 1233504586, 1001, 1001 },
        { "./subdir/README",    S_IFREG | 0664, 1231975636, 1002, 1001 },
        { "./subdir/config",    S_IFREG | 0664, 1232266273, 1003, 1003 },
diff --git a/libarchive/test/test_write_format_mtree_classic.c b/libarchive/test/test_write_format_mtree_classic.c
new file mode 100644 (file)
index 0000000..5176e9c
--- /dev/null
@@ -0,0 +1,194 @@
+/*-
+ * Copyright (c) 2012 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+static char buff[4096];
+static struct {
+       const char      *path;
+       mode_t           mode;
+       int              nlink;
+       time_t           mtime;
+       uid_t            uid;
+       gid_t            gid;
+} entries[] = {
+       { ".",                  S_IFDIR | 0755, 3, 1231975636, 1001, 1001 },
+       { "./COPYING",          S_IFREG | 0644, 1, 1231975636, 1001, 1001 },
+       { "./Makefile",         S_IFREG | 0644, 1, 1233041050, 1001, 1001 },
+       { "./NEWS",             S_IFREG | 0644, 1, 1231975636, 1001, 1001 },
+       { "./PROJECTS",         S_IFREG | 0644, 1, 1231975636, 1001, 1001 },
+       { "./README",           S_IFREG | 0644, 1, 1231975636, 1001, 1001 },
+       { "./subdir",           S_IFDIR | 0755, 3, 1233504586, 1001, 1001 },
+       { "./subdir/README",    S_IFREG | 0664, 1, 1231975636, 1002, 1001 },
+       { "./subdir/config",    S_IFREG | 0664, 1, 1232266273, 1003, 1003 },
+       { "./subdir2",          S_IFDIR | 0755, 3, 1233504586, 1001, 1001 },
+       { "./subdir3",          S_IFDIR | 0755, 3, 1233504586, 1001, 1001 },
+       { "./subdir3/mtree",    S_IFREG | 0664, 2, 1232266273, 1003, 1003 },
+       { NULL, 0, 0, 0, 0, 0 }
+};
+
+static const char image [] = {
+"#mtree\n"
+"/set type=file uid=1001 gid=1001 mode=644\n"
+"\n"
+"# .\n"
+".               time=1231975636.0 mode=755 type=dir\n"
+"    COPYING     time=1231975636.0 size=8\n"
+"    Makefile    time=1233041050.0 size=8\n"
+"    NEWS        time=1231975636.0 size=8\n"
+"    PROJECTS    time=1231975636.0 size=8\n"
+"    README      time=1231975636.0 size=8\n"
+"\n"
+"# ./subdir\n"
+"/set mode=664\n"
+"subdir          time=1233504586.0 mode=755 type=dir\n"
+"    README      time=1231975636.0 uid=1002 size=8\n"
+"    config      time=1232266273.0 gid=1003 uid=1003 size=8\n"
+"# ./subdir\n"
+"..\n"
+"\n"
+"\n"
+"# ./subdir2\n"
+"subdir2         time=1233504586.0 mode=755 type=dir\n"
+"# ./subdir2\n"
+"..\n"
+"\n"
+"\n"
+"# ./subdir3\n"
+"subdir3         time=1233504586.0 mode=755 type=dir\n"
+"    mtree       nlink=2 time=1232266273.0 gid=1003 uid=1003 size=8\n"
+"# ./subdir3\n"
+"..\n"
+"\n"
+"..\n\n"
+};
+
+static const char image_dironly [] = {
+"#mtree\n"
+"/set type=dir uid=1001 gid=1001 mode=755\n"
+"# .\n"
+".               time=1231975636.0\n"
+"# ./subdir\n"
+"subdir          time=1233504586.0\n"
+"# ./subdir\n"
+"..\n"
+"# ./subdir2\n"
+"subdir2         time=1233504586.0\n"
+"# ./subdir2\n"
+"..\n"
+"# ./subdir3\n"
+"subdir3         time=1233504586.0\n"
+"# ./subdir3\n"
+"..\n"
+"..\n"
+};
+
+static void
+test_write_format_mtree_sub(int dironly)
+{
+       struct archive_entry *ae;
+       struct archive* a;
+       size_t used;
+       int i;
+
+       /* Create a mtree format archive. */
+       assert((a = archive_write_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_mtree_classic(a));
+       if (dironly)
+               assertEqualIntA(a, ARCHIVE_OK,
+                       archive_write_set_format_option(a, NULL, "dironly", "1"));
+       assertEqualIntA(a, ARCHIVE_OK,
+               archive_write_open_memory(a, buff, sizeof(buff)-1, &used));
+
+       /* Write entries */
+       for (i = 0; entries[i].path != NULL; i++) {
+               assert((ae = archive_entry_new()) != NULL);
+               archive_entry_set_nlink(ae, entries[i].nlink);
+               assertEqualInt(entries[i].nlink, archive_entry_nlink(ae));
+               archive_entry_set_mtime(ae, entries[i].mtime, 0);
+               assertEqualInt(entries[i].mtime, archive_entry_mtime(ae));
+               archive_entry_set_mode(ae, entries[i].mode);
+               assertEqualInt(entries[i].mode, archive_entry_mode(ae));
+               archive_entry_set_uid(ae, entries[i].uid);
+               assertEqualInt(entries[i].uid, archive_entry_uid(ae));
+               archive_entry_set_gid(ae, entries[i].gid);
+               assertEqualInt(entries[i].gid, archive_entry_gid(ae));
+               archive_entry_copy_pathname(ae, entries[i].path);
+               if ((entries[i].mode & AE_IFMT) != S_IFDIR)
+                       archive_entry_set_size(ae, 8);
+               assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+               if ((entries[i].mode & AE_IFMT) != S_IFDIR)
+                       assertEqualIntA(a, 8,
+                           archive_write_data(a, "Hello012", 15));
+               archive_entry_free(ae);
+       }
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+        assertEqualInt(ARCHIVE_OK, archive_write_free(a));
+
+       buff[used] = '\0';
+       if (dironly)
+               assertEqualString(buff, image_dironly);
+       else
+               assertEqualString(buff, image);
+
+       /*
+        * Read the data and check it.
+        */
+       assert((a = archive_read_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_open_memory(a, buff, used));
+
+       /* Read entries */
+       for (i = 0; entries[i].path != NULL; i++) {
+               if (dironly && (entries[i].mode & AE_IFMT) != S_IFDIR)
+                       continue;
+               assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+               assertEqualInt(entries[i].mtime, archive_entry_mtime(ae));
+               assertEqualInt(entries[i].mode, archive_entry_mode(ae));
+               assertEqualInt(entries[i].uid, archive_entry_uid(ae));
+               assertEqualInt(entries[i].gid, archive_entry_gid(ae));
+               if (i > 0)
+                       assertEqualString(entries[i].path + 2,
+                               archive_entry_pathname(ae));
+               else
+                       assertEqualString(entries[i].path,
+                               archive_entry_pathname(ae));
+               if ((entries[i].mode & AE_IFMT) != S_IFDIR)
+                       assertEqualInt(8, archive_entry_size(ae));
+       }
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
+DEFINE_TEST(test_write_format_mtree_classic)
+{
+       /* Generate classic format. */
+       test_write_format_mtree_sub(0);
+       /* Generate classic format and Write directory only. */
+       test_write_format_mtree_sub(1);
+}
diff --git a/libarchive/test/test_write_format_mtree_classic_indent.c b/libarchive/test/test_write_format_mtree_classic_indent.c
new file mode 100644 (file)
index 0000000..29247fd
--- /dev/null
@@ -0,0 +1,196 @@
+/*-
+ * Copyright (c) 2012 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer
+ *    in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+static char buff[4096];
+static struct {
+       const char      *path;
+       mode_t           mode;
+       int              nlink;
+       time_t           mtime;
+       uid_t            uid;
+       gid_t            gid;
+} entries[] = {
+       { ".",                  S_IFDIR | 0755, 3, 1231975636, 1001, 1001 },
+       { "./COPYING",          S_IFREG | 0644, 1, 1231975636, 1001, 1001 },
+       { "./Makefile",         S_IFREG | 0644, 1, 1233041050, 1001, 1001 },
+       { "./NEWS",             S_IFREG | 0644, 1, 1231975636, 1001, 1001 },
+       { "./PROJECTS",         S_IFREG | 0644, 1, 1231975636, 1001, 1001 },
+       { "./README",           S_IFREG | 0644, 1, 1231975636, 1001, 1001 },
+       { "./subdir",           S_IFDIR | 0755, 3, 1233504586, 1001, 1001 },
+       { "./subdir/README",    S_IFREG | 0664, 1, 1231975636, 1002, 1001 },
+       { "./subdir/config",    S_IFREG | 0664, 1, 1232266273, 1003, 1003 },
+       { "./subdir2",          S_IFDIR | 0755, 3, 1233504586, 1001, 1001 },
+       { "./subdir3",          S_IFDIR | 0755, 3, 1233504586, 1001, 1001 },
+       { "./subdir3/mtree",    S_IFREG | 0664, 2, 1232266273, 1003, 1003 },
+       { NULL, 0, 0, 0, 0, 0 }
+};
+
+static const char image [] = {
+"#mtree\n"
+"/set type=file uid=1001 gid=1001 mode=644\n"
+"\n"
+"# .\n"
+".               time=1231975636.0 mode=755 type=dir\n"
+"    COPYING         time=1231975636.0 size=8\n"
+"    Makefile        time=1233041050.0 size=8\n"
+"    NEWS            time=1231975636.0 size=8\n"
+"    PROJECTS        time=1231975636.0 size=8\n"
+"    README          time=1231975636.0 size=8\n"
+"\n"
+"# ./subdir\n"
+"/set mode=664\n"
+"    subdir          time=1233504586.0 mode=755 type=dir\n"
+"        README          time=1231975636.0 uid=1002 size=8\n"
+"        config          time=1232266273.0 gid=1003 uid=1003 size=8\n"
+"    # ./subdir\n"
+"    ..\n"
+"\n"
+"\n"
+"# ./subdir2\n"
+"    subdir2         time=1233504586.0 mode=755 type=dir\n"
+"    # ./subdir2\n"
+"    ..\n"
+"\n"
+"\n"
+"# ./subdir3\n"
+"    subdir3         time=1233504586.0 mode=755 type=dir\n"
+"        mtree           nlink=2 time=1232266273.0 gid=1003 uid=1003 size=8\n"
+"    # ./subdir3\n"
+"    ..\n"
+"\n"
+"..\n\n"
+};
+
+static const char image_dironly [] = {
+"#mtree\n"
+"/set type=dir uid=1001 gid=1001 mode=755\n"
+"# .\n"
+".               time=1231975636.0\n"
+"# ./subdir\n"
+"    subdir          time=1233504586.0\n"
+"    # ./subdir\n"
+"    ..\n"
+"# ./subdir2\n"
+"    subdir2         time=1233504586.0\n"
+"    # ./subdir2\n"
+"    ..\n"
+"# ./subdir3\n"
+"    subdir3         time=1233504586.0\n"
+"    # ./subdir3\n"
+"    ..\n"
+"..\n"
+};
+
+static void
+test_write_format_mtree_sub(int dironly)
+{
+       struct archive_entry *ae;
+       struct archive* a;
+       size_t used;
+       int i;
+
+       /* Create a mtree format archive. */
+       assert((a = archive_write_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_mtree_classic(a));
+       assertEqualIntA(a, ARCHIVE_OK,
+               archive_write_set_format_option(a, NULL, "indent", "1"));
+       if (dironly)
+               assertEqualIntA(a, ARCHIVE_OK,
+                       archive_write_set_format_option(a, NULL, "dironly", "1"));
+       assertEqualIntA(a, ARCHIVE_OK,
+               archive_write_open_memory(a, buff, sizeof(buff)-1, &used));
+
+       /* Write entries */
+       for (i = 0; entries[i].path != NULL; i++) {
+               assert((ae = archive_entry_new()) != NULL);
+               archive_entry_set_nlink(ae, entries[i].nlink);
+               assertEqualInt(entries[i].nlink, archive_entry_nlink(ae));
+               archive_entry_set_mtime(ae, entries[i].mtime, 0);
+               assertEqualInt(entries[i].mtime, archive_entry_mtime(ae));
+               archive_entry_set_mode(ae, entries[i].mode);
+               assertEqualInt(entries[i].mode, archive_entry_mode(ae));
+               archive_entry_set_uid(ae, entries[i].uid);
+               assertEqualInt(entries[i].uid, archive_entry_uid(ae));
+               archive_entry_set_gid(ae, entries[i].gid);
+               assertEqualInt(entries[i].gid, archive_entry_gid(ae));
+               archive_entry_copy_pathname(ae, entries[i].path);
+               if ((entries[i].mode & AE_IFMT) != S_IFDIR)
+                       archive_entry_set_size(ae, 8);
+               assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+               if ((entries[i].mode & AE_IFMT) != S_IFDIR)
+                       assertEqualIntA(a, 8,
+                           archive_write_data(a, "Hello012", 15));
+               archive_entry_free(ae);
+       }
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+        assertEqualInt(ARCHIVE_OK, archive_write_free(a));
+
+       buff[used] = '\0';
+       if (dironly)
+               assertEqualString(buff, image_dironly);
+       else
+               assertEqualString(buff, image);
+
+       /*
+        * Read the data and check it.
+        */
+       assert((a = archive_read_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_open_memory(a, buff, used));
+
+       /* Read entries */
+       for (i = 0; entries[i].path != NULL; i++) {
+               if (dironly && (entries[i].mode & AE_IFMT) != S_IFDIR)
+                       continue;
+               assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+               assertEqualInt(entries[i].mtime, archive_entry_mtime(ae));
+               assertEqualInt(entries[i].mode, archive_entry_mode(ae));
+               assertEqualInt(entries[i].uid, archive_entry_uid(ae));
+               assertEqualInt(entries[i].gid, archive_entry_gid(ae));
+               if (i > 0)
+                       assertEqualString(entries[i].path + 2,
+                               archive_entry_pathname(ae));
+               else
+                       assertEqualString(entries[i].path,
+                               archive_entry_pathname(ae));
+               if ((entries[i].mode & AE_IFMT) != S_IFDIR)
+                       assertEqualInt(8, archive_entry_size(ae));
+       }
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
+DEFINE_TEST(test_write_format_mtree_classic_indent)
+{
+       /* Generate classic format. */
+       test_write_format_mtree_sub(0);
+       /* Generate classic format and Write directory only. */
+       test_write_format_mtree_sub(1);
+}
index 18689fe855def20f81b5f09ccd7c50d92a58a106..820d86f70b3db1ad0f5fbfd679fb9b69a4d3ff32 100644 (file)
@@ -41,6 +41,7 @@ static struct {
        unsigned long    fflags;
 } entries[] = {
        { "./f1",       UF_IMMUTABLE | UF_NODUMP },
+       { "./f11",      UF_IMMUTABLE | UF_NODUMP },
        { "./f2",       0 },
        { "./f3",       UF_NODUMP },
        { NULL, 0 }