]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add xar writer.
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Fri, 28 May 2010 17:29:06 +0000 (13:29 -0400)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Fri, 28 May 2010 17:29:06 +0000 (13:29 -0400)
SVN-Revision: 2417

13 files changed:
CMakeLists.txt
Makefile.am
build/cmake/config.h.in
configure.ac
libarchive/CMakeLists.txt
libarchive/archive.h
libarchive/archive_entry_link_resolver.c
libarchive/archive_write_set_format.c
libarchive/archive_write_set_format_by_name.c
libarchive/archive_write_set_format_xar.c [new file with mode: 0644]
libarchive/test/CMakeLists.txt
libarchive/test/test_write_format_xar.c [new file with mode: 0644]
libarchive/test/test_write_format_xar_empty.c [new file with mode: 0644]

index 66ece60e086eb9af39549af43a9fa652418e6811..c1eba4d2bdb6e9098d0343c988b3c505dbda4590 100644 (file)
@@ -454,6 +454,7 @@ IF(LIBXML2_FOUND)
   INCLUDE_DIRECTORIES(${ICONV_INCLUDE_DIR})
   SET(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR} ${LIBXML2_INCLUDE_DIR})
   CHECK_INCLUDE_FILES("libxml/xmlreader.h" HAVE_LIBXML_XMLREADER_H)
+  CHECK_INCLUDE_FILES("libxml/xmlwriter.h" HAVE_LIBXML_XMLWRITER_H)
   SET(CMAKE_REQUIRED_INCLUDES "")
 ELSE(LIBXML2_FOUND)
   #
@@ -490,6 +491,7 @@ CHECK_FUNCTION_EXISTS_GLIBC(geteuid HAVE_GETEUID)
 CHECK_FUNCTION_EXISTS_GLIBC(getgrnam_r HAVE_GETGRNAM_R)
 CHECK_FUNCTION_EXISTS_GLIBC(getpwnam_r HAVE_GETPWNAM_R)
 CHECK_FUNCTION_EXISTS_GLIBC(getpid HAVE_GETPID)
+CHECK_FUNCTION_EXISTS_GLIBC(gmtime_r HAVE_GMTIME_R)
 CHECK_FUNCTION_EXISTS_GLIBC(lchflags HAVE_LCHFLAGS)
 CHECK_FUNCTION_EXISTS_GLIBC(lchmod HAVE_LCHMOD)
 CHECK_FUNCTION_EXISTS_GLIBC(lchown HAVE_LCHOWN)
index 928f12ae601be1c9b0fe9b21f336a639420a2be9..9de8a9d909050a04a233550e8bbfc74707e9be44 100644 (file)
@@ -170,6 +170,7 @@ libarchive_la_SOURCES=                                              \
        libarchive/archive_write_set_format_shar.c              \
        libarchive/archive_write_set_format_ustar.c             \
        libarchive/archive_write_set_format_gnutar.c            \
+       libarchive/archive_write_set_format_xar.c               \
        libarchive/archive_write_set_format_zip.c               \
        libarchive/archive_write_set_options.c                  \
        libarchive/config_freebsd.h                             \
@@ -347,6 +348,8 @@ libarchive_test_SOURCES=                                    \
        libarchive/test/test_write_format_tar.c                 \
        libarchive/test/test_write_format_tar_empty.c           \
        libarchive/test/test_write_format_tar_ustar.c           \
+       libarchive/test/test_write_format_xar.c                 \
+       libarchive/test/test_write_format_xar_empty.c           \
        libarchive/test/test_write_format_zip.c                 \
        libarchive/test/test_write_format_zip_empty.c           \
        libarchive/test/test_write_format_zip_no_compression.c  \
index ed1de814b506dd585db2cc6472d1064525ccd9d7..1a1d919d73ae8acb74028e9401ec5281ff33ab76 100644 (file)
 /* Define to 1 if you have the `getxattr' function. */
 #cmakedefine HAVE_GETXATTR 1
 
+/* Define to 1 if you have the `gmtime_r' function. */
+#cmakedefine HAVE_GMTIME_R 1
+
 /* Define to 1 if you have the <grp.h> header file. */
 #cmakedefine HAVE_GRP_H 1
 
 /* Define to 1 if you have the <libxml/xmlreader.h> header file. */
 #cmakedefine HAVE_LIBXML_XMLREADER_H 1
 
+/* Define to 1 if you have the <libxml/xmlwriter.h> header file. */
+#cmakedefine HAVE_LIBXML_XMLWRITER_H 1
+
 /* Define to 1 if you have the `z' library (-lz). */
 #cmakedefine HAVE_LIBZ 1
 
index da09e37788c08fee09dee6640480e292ee2ccf26..169192a92b6ba82cfcdd92fa6129f6d33b649dd2 100644 (file)
@@ -250,7 +250,7 @@ if test "x$with_xml2" != "xno"; then
     CPPFLAGS="${CPPFLAGS} `${XML2_CONFIG} --cflags`"
     LIBS="${LIBS} `${XML2_CONFIG} --libs`"
   fi
-  AC_CHECK_HEADERS([libxml/xmlreader.h])
+  AC_CHECK_HEADERS([libxml/xmlreader.h libxml/xmlwriter.h])
   AC_CHECK_LIB(xml2,xmlInitParser)
 fi
 if test "x$ac_cv_header_libxml_xmlreader_h" != "xyes"; then
@@ -414,7 +414,7 @@ AC_CHECK_STDCALL_FUNC([CreateHardLinkA],[const char *, const char *, void *])
 AC_CHECK_FUNCS([chflags chown chroot ctime_r])
 AC_CHECK_FUNCS([fchdir fchflags fchmod fchown fcntl fork])
 AC_CHECK_FUNCS([fstat ftruncate futimens futimes geteuid getpid])
-AC_CHECK_FUNCS([getgrnam_r getpwnam_r])
+AC_CHECK_FUNCS([getgrnam_r getpwnam_r gmtime_r])
 AC_CHECK_FUNCS([lchflags lchmod lchown link localtime_r lstat])
 AC_CHECK_FUNCS([lutimes mbrtowc memmove memset mkdir mkfifo mknod mkstemp])
 AC_CHECK_FUNCS([nl_langinfo pipe poll readlink])
index 5459c6acc1ac96457e6a948acf168afac5a22b34..4d5f6ea6f2975ebfd9058f0bca2fb694fe2553ef 100644 (file)
@@ -91,6 +91,7 @@ SET(libarchive_SOURCES
   archive_write_set_format_pax.c
   archive_write_set_format_shar.c
   archive_write_set_format_ustar.c
+  archive_write_set_format_xar.c
   archive_write_set_format_zip.c
   archive_write_set_options.c
   filter_fork.c
index 4776249b0b25775d51afc60b27a1ffc0e1f2239d..22dee5570e8fd7f2c1b200d0b7a8192b594a4914 100644 (file)
@@ -592,6 +592,7 @@ __LA_DECL int archive_write_set_format_pax_restricted(struct archive *);
 __LA_DECL int archive_write_set_format_shar(struct archive *);
 __LA_DECL int archive_write_set_format_shar_dump(struct archive *);
 __LA_DECL int archive_write_set_format_ustar(struct archive *);
+__LA_DECL int archive_write_set_format_xar(struct archive *);
 __LA_DECL int archive_write_set_format_zip(struct archive *);
 __LA_DECL int archive_write_open(struct archive *, void *,
                     archive_open_callback *, archive_write_callback *,
index ebd2f76b4a7640fa87ebbd8e75e076d2562a47d7..07f300e2ec630a36152166085bb053221957d2ef 100644 (file)
@@ -146,6 +146,7 @@ archive_entry_linkresolver_set_strategy(struct archive_entry_linkresolver *res,
        case ARCHIVE_FORMAT_ISO9660:
        case ARCHIVE_FORMAT_SHAR:
        case ARCHIVE_FORMAT_TAR:
+       case ARCHIVE_FORMAT_XAR:
                res->strategy = ARCHIVE_ENTRY_LINKIFY_LIKE_TAR;
                break;
        default:
index ac348f83b84c48ae88acd51b7f4479586e856995..57acc75403910281ff74d6be21f7e18fda008146 100644 (file)
@@ -55,6 +55,7 @@ struct { int code; int (*setter)(struct archive *); } codes[] =
        { ARCHIVE_FORMAT_TAR_PAX_RESTRICTED,
                                archive_write_set_format_pax_restricted },
        { ARCHIVE_FORMAT_TAR_USTAR,     archive_write_set_format_ustar },
+       { ARCHIVE_FORMAT_XAR,           archive_write_set_format_xar },
        { ARCHIVE_FORMAT_ZIP,   archive_write_set_format_zip },
        { 0,            NULL }
 };
index a78d93a6fc604cf5bbfb2f3c420bf55464be0067..6d76d030d776f1d2ac704fd334cfb742bb30d3b0 100644 (file)
@@ -64,6 +64,7 @@ struct { const char *name; int (*setter)(struct archive *); } names[] =
        { "shar",       archive_write_set_format_shar },
        { "shardump",   archive_write_set_format_shar_dump },
        { "ustar",      archive_write_set_format_ustar },
+       { "xar",        archive_write_set_format_xar },
        { "zip",        archive_write_set_format_zip },
        { NULL,         NULL }
 };
diff --git a/libarchive/archive_write_set_format_xar.c b/libarchive/archive_write_set_format_xar.c
new file mode 100644 (file)
index 0000000..f4e431d
--- /dev/null
@@ -0,0 +1,3217 @@
+/*-
+ * Copyright (c) 2010 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.
+ * 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 "archive_platform.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+#if HAVE_LIBXML_XMLWRITER_H
+#include <libxml/xmlwriter.h>
+#endif
+#ifdef HAVE_BZLIB_H
+#include <bzlib.h>
+#endif
+#if HAVE_LZMA_H
+#include <lzma.h>
+#endif
+#ifdef HAVE_ZLIB_H
+#include <zlib.h>
+#endif
+
+#include "archive.h"
+#include "archive_endian.h"
+#include "archive_entry.h"
+#include "archive_hash.h"
+#include "archive_private.h"
+#include "archive_rb.h"
+#include "archive_string.h"
+#include "archive_write_private.h"
+
+/*
+ * Differences to xar utility.
+ * - Subdocument is not supported yet.
+ * - ACL is not supported yet.
+ * - When writing an XML element <link type="<file-type>">, <file-type> which
+ *   is a file type a symbolic link is referencing is always marked as "broken".
+ *   xar utility uses stat(2) to get the file type, but, in libarcive format writer,
+ *   we should not use it; if it is needed, we should get it at archive_read_disk.c.
+ * - It is possible to appear both <flags> and <ext2> elements.
+ *   Xar utility generates <flags> on BSD platform and <ext2> on Linux platform.
+ *
+ */
+
+#if !defined(HAVE_LIBXML_XMLWRITER_H) ||\
+       !defined(HAVE_ZLIB_H) || \
+       !defined(ARCHIVE_HAS_MD5) || !defined(ARCHIVE_HAS_SHA1)
+/*
+ * xar needs several external libraries.
+ *   o libxml2
+ *   o openssl or MD5/SHA1 hash function
+ *   o zlib
+ *   o bzlib2 (option)
+ *   o liblzma (option)
+ */
+int
+archive_write_set_format_xar(struct archive *_a)
+{
+       struct archive_write *a = (struct archive_write *)_a;
+
+       archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+           "Xar not supported on this platform");
+       return (ARCHIVE_WARN);
+}
+
+#else  /* Support xar format */
+
+//#define DEBUG_PRINT_TOC              1
+
+#define HEADER_MAGIC   0x78617221
+#define HEADER_SIZE    28
+#define HEADER_VERSION 1
+
+enum sumalg {
+       CKSUM_NONE = 0,
+       CKSUM_SHA1 = 1,
+       CKSUM_MD5 = 2
+};
+
+#define MD5_SIZE       16
+#define SHA1_SIZE      20
+#define MAX_SUM_SIZE   20
+#define MD5_NAME       "md5"
+#define SHA1_NAME      "sha1"
+enum enctype {
+       NONE,
+       GZIP,
+       BZIP2,
+       LZMA,
+       XZ,
+};
+
+struct chksumwork {
+       enum sumalg              alg;
+#ifdef ARCHIVE_HAS_MD5
+       archive_md5_ctx          md5ctx;
+#endif
+#ifdef ARCHIVE_HAS_SHA1
+       archive_sha1_ctx         sha1ctx;
+#endif
+};
+
+enum la_zaction {
+       ARCHIVE_Z_FINISH,
+       ARCHIVE_Z_RUN
+};
+
+/*
+ * Universal zstream.
+ */
+struct la_zstream {
+       const unsigned char     *next_in;
+       size_t                   avail_in;
+       uint64_t                 total_in;
+
+       unsigned char           *next_out;
+       size_t                   avail_out;
+       uint64_t                 total_out;
+
+       int                      valid;
+       void                    *real_stream;
+       int                      (*code) (struct archive *a,
+                                   struct la_zstream *lastrm,
+                                   enum la_zaction action);
+       int                      (*end)(struct archive *a,
+                                   struct la_zstream *lastrm);
+};
+
+struct chksumval {
+       enum sumalg              alg;
+       size_t                   len;
+       unsigned char            val[MAX_SUM_SIZE];
+};
+
+struct heap_data {
+       int                      id;
+       struct heap_data        *next;
+       uint64_t                 temp_offset;
+       uint64_t                 length;        /* archived size.       */
+       uint64_t                 size;          /* extracted size.      */
+       enum enctype             compression;
+       struct chksumval         a_sum;         /* archived checksum.   */
+       struct chksumval         e_sum;         /* extracted checksum.  */
+};
+
+struct file {
+       struct archive_rb_node   rbnode;
+
+       int                      id;
+       struct archive_entry    *entry;
+
+       struct archive_rb_tree   rbtree;
+       struct file             *next;
+       struct file             *chnext;
+       struct file             *hlnext;
+       /* For hardlinked files.
+        * Use only when archive_entry_nlink() > 1 */
+       struct file             *hardlink_target;
+       struct file             *parent;        /* parent directory entry */
+       /*
+        * To manage sub directory files.
+        * We use 'chnext' a menber of struct file to chain.
+        */
+       struct {
+               struct file     *first;
+               struct file     **last;
+       }                        children;
+
+       /* For making a directory tree. */
+        struct archive_string    parentdir;
+        struct archive_string    basename;
+        struct archive_string    symlink;
+
+       int                      ea_idx;
+       struct {
+               struct heap_data *first;
+               struct heap_data **last;
+       }                        xattr;
+       struct heap_data         data;
+        struct archive_string    script;
+
+       int                      virtual:1;
+       int                      dir:1;
+};
+
+struct hardlink {
+       struct archive_rb_node   rbnode;
+       int                      nlink;
+       struct {
+               struct file     *first;
+               struct file     **last;
+       }                        file_list;
+};
+
+struct xar {
+       int                      temp_fd;
+       uint64_t                 temp_offset;
+
+       int                      file_idx;
+       struct file             *root;
+       struct file             *cur_dirent;
+       struct archive_string    cur_dirstr;
+       struct file             *cur_file;
+       uint64_t                 bytes_remaining;
+       struct archive_string    tstr;
+       struct archive_string    vstr;
+
+       enum sumalg              opt_toc_sumalg;
+       enum sumalg              opt_sumalg;
+       enum enctype             opt_compression;
+       int                      opt_compression_level;
+
+       struct chksumwork        a_sumwrk;      /* archived checksum.   */
+       struct chksumwork        e_sumwrk;      /* extracted checksum.  */
+       struct la_zstream        stream;
+       /*
+        * Compressed data buffer.
+        */
+       unsigned char            wbuff[1024 * 64];
+       size_t                   wbuff_remaining;
+
+       struct heap_data         toc;
+       /*
+        * The list of all file entries is used to manage struct file
+        * objects.
+        * We use 'next' a menber of struct file to chain.
+        */
+       struct {
+               struct file     *first;
+               struct file     **last;
+       }                        file_list;
+       /*
+        * The list of hard-linked file entries.
+        * We use 'hlnext' a menber of struct file to chain.
+        */
+       struct archive_rb_tree   hardlink_rbtree;
+};
+
+static int     xar_options(struct archive_write *,
+                   const char *, const char *);
+static int     xar_write_header(struct archive_write *,
+                   struct archive_entry *);
+static ssize_t xar_write_data(struct archive_write *,
+                   const void *, size_t);
+static int     xar_finish_entry(struct archive_write *);
+static int     xar_close(struct archive_write *);
+static int     xar_free(struct archive_write *);
+
+static struct file *file_new(struct archive_entry *);
+static void    file_free(struct file *);
+static struct file *file_create_virtual_dir(struct xar *,
+                   const char *);
+static int     file_add_child_tail(struct file *, struct file *);
+static struct file *file_find_child(struct file *, const char *);
+static int     file_gen_utility_names(struct archive_write *,
+                   struct file *);
+static int     get_path_component(char *, int, const char *);
+static int     file_tree(struct archive_write *, struct file **);
+static void    file_register(struct xar *, struct file *);
+static void    file_init_register(struct xar *);
+static void    file_free_register(struct xar *);
+static int     file_register_hardlink(struct archive_write *,
+                   struct file *);
+static void    file_connect_hardlink_files(struct xar *);
+static void    file_init_hardlinks(struct xar *);
+static void    file_free_hardlinks(struct xar *);
+
+static void    checksum_init(struct chksumwork *, enum sumalg);
+static void    checksum_update(struct chksumwork *, const void *, size_t);
+static void    checksum_final(struct chksumwork *, struct chksumval *);
+static int     compression_init_encoder_gzip(struct archive *,
+                   struct la_zstream *, int, int);
+static int     compression_code_gzip(struct archive *,
+                   struct la_zstream *, enum la_zaction);
+static int     compression_end_gzip(struct archive *, struct la_zstream *);
+static int     compression_init_encoder_bzip2(struct archive *,
+                   struct la_zstream *, int);
+#if defined(HAVE_BZLIB_H)
+static int     compression_code_bzip2(struct archive *,
+                   struct la_zstream *, enum la_zaction);
+static int     compression_end_bzip2(struct archive *, struct la_zstream *);
+#endif
+static int     compression_init_encoder_lzma(struct archive *,
+                   struct la_zstream *, int);
+static int     compression_init_encoder_xz(struct archive *,
+                   struct la_zstream *, int);
+#if defined(HAVE_LZMA_H)
+static int     compression_code_lzma(struct archive *,
+                   struct la_zstream *, enum la_zaction);
+static int     compression_end_lzma(struct archive *, struct la_zstream *);
+#endif
+static int     xar_compression_init_encoder(struct archive_write *);
+static int     compression_code(struct archive *,
+                   struct la_zstream *, enum la_zaction);
+static int     compression_end(struct archive *,
+                   struct la_zstream *);
+static int     save_xattrs(struct archive_write *, struct file *);
+static size_t  utf8_encode(struct archive_string *, const wchar_t *);
+static int     getalgsize(enum sumalg);
+static const char *getalgname(enum sumalg);
+
+int
+archive_write_set_format_xar(struct archive *_a)
+{
+       struct archive_write *a = (struct archive_write *)_a;
+       struct xar *xar;
+
+       archive_check_magic(_a, ARCHIVE_WRITE_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_write_set_format_xar");
+
+       /* If another format was already registered, unregister it. */
+       if (a->format_free != NULL)
+               (a->format_free)(a);
+
+       xar = calloc(1, sizeof(*xar));
+       if (xar == NULL) {
+               archive_set_error(&a->archive, ENOMEM,
+                   "Can't allocate xar data");
+               return (ARCHIVE_FATAL);
+       }
+       xar->temp_fd = -1;
+       file_init_register(xar);
+       file_init_hardlinks(xar);
+       archive_string_init(&(xar->tstr));
+       archive_string_init(&(xar->vstr));
+
+       /*
+        * Create the root directory.
+        */
+       xar->root = file_create_virtual_dir(xar, "");
+       if (xar->root == NULL) {
+               free(xar);
+               archive_set_error(&a->archive, ENOMEM,
+                   "Can't allocate xar data");
+               return (ARCHIVE_FATAL);
+       }
+       xar->root->parent = xar->root;
+       file_register(xar, xar->root);
+       xar->cur_dirent = xar->root;
+       archive_string_init(&(xar->cur_dirstr));
+       archive_string_ensure(&(xar->cur_dirstr), 1);
+       xar->cur_dirstr.s[0] = 0;
+
+       /*
+        * Initialize option.
+        */
+       /* Set default checksum type. */
+       xar->opt_toc_sumalg = CKSUM_SHA1;
+       xar->opt_sumalg = CKSUM_SHA1;
+       /* Set default compression type and level. */
+       xar->opt_compression = GZIP;
+       xar->opt_compression_level = 6;
+
+       a->format_data = xar;
+
+       a->format_name = "xar";
+       a->format_options = xar_options;
+       a->format_write_header = xar_write_header;
+       a->format_write_data = xar_write_data;
+       a->format_finish_entry = xar_finish_entry;
+       a->format_close = xar_close;
+       a->format_free = xar_free;
+       a->archive.archive_format = ARCHIVE_FORMAT_XAR;
+       a->archive.archive_format_name = "xar";
+
+       return (ARCHIVE_OK);
+}
+
+static int
+xar_options(struct archive_write *a, const char *key, const char *value)
+{
+       struct xar *xar;
+
+       xar = (struct xar *)a->format_data;
+
+       if (strcmp(key, "checksum") == 0) {
+               if (value == NULL)
+                       xar->opt_sumalg = CKSUM_NONE;
+               else if (strcmp(value, "sha1") == 0)
+                       xar->opt_sumalg = CKSUM_SHA1;
+               else if (strcmp(value, "md5") == 0)
+                       xar->opt_sumalg = CKSUM_MD5;
+               else {
+                       archive_set_error(&(a->archive),
+                           ARCHIVE_ERRNO_MISC,
+                           "Unkonwn checksum name: `%s'",
+                           value);
+                       return (ARCHIVE_FAILED);
+               }
+               return (ARCHIVE_OK);
+       }
+       if (strcmp(key, "compression") == 0) {
+               const char *name = NULL;
+
+               if (value == NULL)
+                       xar->opt_compression = NONE;
+               else if (strcmp(value, "gzip") == 0)
+                       xar->opt_compression = GZIP;
+               else if (strcmp(value, "bzip2") == 0)
+#ifdef HAVE_BZLIB_H
+                       xar->opt_compression = BZIP2;
+#else
+                       name = "bzip2";
+#endif
+               else if (strcmp(value, "lzma") == 0)
+#if HAVE_LZMA_H
+                       xar->opt_compression = LZMA;
+#else
+                       name = "lzma";
+#endif
+               else if (strcmp(value, "xz") == 0)
+#if HAVE_LZMA_H
+                       xar->opt_compression = XZ;
+#else
+                       name = "xz";
+#endif
+               else {
+                       archive_set_error(&(a->archive),
+                           ARCHIVE_ERRNO_MISC,
+                           "Unkonwn compression name: `%s'",
+                           value);
+                       return (ARCHIVE_FAILED);
+               }
+               if (name != NULL) {
+                       archive_set_error(&(a->archive),
+                           ARCHIVE_ERRNO_MISC,
+                           "`%s' compression not supported "
+                           "on this platform",
+                           name);
+                       return (ARCHIVE_FAILED);
+               }
+               return (ARCHIVE_OK);
+       }
+       if (strcmp(key, "compression-level") == 0) {
+               if (value == NULL ||
+                   !(value[0] >= '0' && value[0] <= '9') ||
+                   value[1] != '\0') {
+                       archive_set_error(&(a->archive),
+                           ARCHIVE_ERRNO_MISC,
+                           "Illeagal value `%s'",
+                           value);
+                       return (ARCHIVE_FAILED);
+               }
+               xar->opt_compression_level = value[0] - '0';
+               return (ARCHIVE_OK);
+       }
+       if (strcmp(key, "toc-checksum") == 0) {
+               if (value == NULL)
+                       xar->opt_toc_sumalg = CKSUM_NONE;
+               else if (strcmp(value, "sha1") == 0)
+                       xar->opt_toc_sumalg = CKSUM_SHA1;
+               else if (strcmp(value, "md5") == 0)
+                       xar->opt_toc_sumalg = CKSUM_MD5;
+               else {
+                       archive_set_error(&(a->archive),
+                           ARCHIVE_ERRNO_MISC,
+                           "Unkonwn checksum name: `%s'",
+                           value);
+                       return (ARCHIVE_FAILED);
+               }
+               return (ARCHIVE_OK);
+       }
+
+       return (ARCHIVE_WARN);
+}
+
+static int
+xar_write_header(struct archive_write *a, struct archive_entry *entry)
+{
+       struct xar *xar;
+       struct file *file;
+       struct archive_entry *file_entry;
+       int r, r2;
+
+       xar = (struct xar *)a->format_data;
+       xar->cur_file = NULL;
+       xar->bytes_remaining = 0;
+
+       file = file_new(entry);
+       if (file == NULL) {
+               archive_set_error(&a->archive, ENOMEM,
+                   "Can't allocate data");
+               return (ARCHIVE_FATAL);
+       }
+       r2 = file_gen_utility_names(a, file);
+
+       /* Add entry into tree */
+       file_entry = file->entry;
+       r = file_tree(a, &file);
+       if (r != ARCHIVE_OK)
+               return (r);
+       /* There is the same file in tree and
+        * the current file is older than the file in tree.
+        * So we don't need the current file data anymore. */
+       if (file->entry != file_entry)
+               return (r2);
+       if (file->id == 0)
+               file_register(xar, file);
+
+       /* A virtual file, which is a directory, does not have
+        * any contents and we won't store it into a archive
+        * file other than its name. */
+       if (file->virtual)
+               return (r2);
+
+       /*
+        * Prepare to save the contents of the file.
+        */
+       if (xar->temp_fd == -1) {
+               int algsize;
+               xar->temp_offset = 0;
+               xar->temp_fd = __archive_mktemp(NULL);
+               if (xar->temp_fd < 0) {
+                       archive_set_error(&a->archive, errno,
+                           "Couldn't create temporary file");
+                       return (ARCHIVE_FATAL);
+               }
+               algsize = getalgsize(xar->opt_toc_sumalg);
+               if (algsize > 0) {
+                       if (lseek(xar->temp_fd, algsize, SEEK_SET) < 0) {
+                               archive_set_error(&(a->archive), errno,
+                                   "lseek failed");
+                               return (ARCHIVE_FATAL);
+                       }
+                       xar->temp_offset = algsize;
+               }
+       }
+
+       if (archive_entry_hardlink(file->entry) == NULL) {
+               r = save_xattrs(a, file);
+               if (r != ARCHIVE_OK)
+                       return (ARCHIVE_FATAL);
+       }
+
+       /* Non regular files contents are unneeded to be saved to
+        * a temporary file. */
+       if (archive_entry_filetype(file->entry) != AE_IFREG)
+               return (r2);
+
+       /*
+        * Set the current file to cur_file to read its contents.
+        */
+       xar->cur_file = file;
+
+       if (archive_entry_nlink(file->entry) > 1) {
+               r = file_register_hardlink(a, file);
+               if (r != ARCHIVE_OK)
+                       return (r);
+               if (archive_entry_hardlink(file->entry) != NULL) {
+                       archive_entry_unset_size(file->entry);
+                       return (r2);
+               }
+       }
+
+       /* Save a offset of current file in temporary file. */
+       file->data.temp_offset = xar->temp_offset;
+       file->data.size = archive_entry_size(file->entry);
+       file->data.compression = xar->opt_compression;
+       xar->bytes_remaining = archive_entry_size(file->entry);
+       checksum_init(&(xar->a_sumwrk), xar->opt_sumalg);
+       checksum_init(&(xar->e_sumwrk), xar->opt_sumalg);
+       r = xar_compression_init_encoder(a);
+
+       if (r != ARCHIVE_OK)
+               return (r);
+       else
+               return (r2);
+}
+
+static int
+write_to_temp(struct archive_write *a, const void *buff, size_t s)
+{
+       struct xar *xar;
+       unsigned char *p;
+       ssize_t ws;
+
+       xar = (struct xar *)a->format_data;
+       p = (unsigned char *)buff;
+       while (s) {
+               ws = write(xar->temp_fd, p, s);
+               if (ws < 0) {
+                       archive_set_error(&(a->archive), errno,
+                           "fwrite function failed");
+                       return (ARCHIVE_FATAL);
+               }
+               s -= ws;
+               p += ws;
+               xar->temp_offset += ws;
+       }
+       return (ARCHIVE_OK);
+}
+
+static ssize_t
+xar_write_data(struct archive_write *a, const void *buff, size_t s)
+{
+       struct xar *xar;
+       enum la_zaction run;
+       size_t size, rsize;
+       int r;
+
+       xar = (struct xar *)a->format_data;
+
+       if (s > xar->bytes_remaining)
+               s = xar->bytes_remaining;
+       if (s == 0 || xar->cur_file == NULL)
+               return (0);
+       if (xar->cur_file->data.compression == NONE) {
+               checksum_update(&(xar->e_sumwrk), buff, s);
+               checksum_update(&(xar->a_sumwrk), buff, s);
+               size = rsize = s;
+       } else {
+               xar->stream.next_in = (const unsigned char *)buff;
+               xar->stream.avail_in = s;
+               if (xar->bytes_remaining > s)
+                       run = ARCHIVE_Z_RUN;
+               else
+                       run = ARCHIVE_Z_FINISH;
+               /* Compress file data. */
+               r = compression_code(&(a->archive), &(xar->stream), run);
+               if (r != ARCHIVE_OK && r != ARCHIVE_EOF)
+                       return (ARCHIVE_FATAL);
+               rsize = s - xar->stream.avail_in;
+               checksum_update(&(xar->e_sumwrk), buff, rsize);
+               size = sizeof(xar->wbuff) - xar->stream.avail_out;
+               checksum_update(&(xar->a_sumwrk), xar->wbuff, size);
+       }
+#if !defined(_WIN32) || defined(__CYGWIN__)
+       if (xar->bytes_remaining ==
+           archive_entry_size(xar->cur_file->entry)) {
+               /*
+                * Get the path of a shell script if so.
+                */
+               const unsigned char *b = (const unsigned char *)buff;
+
+               archive_string_empty(&(xar->cur_file->script));
+               if (rsize > 2 && b[0] == '#' && b[1] == '!') {
+                       char path[PATH_MAX];
+                       size_t i, end, off;
+
+                       end = sizeof(path);
+                       if (end > rsize)
+                               end = rsize;
+                       off = 2;
+                       if (b[off] == ' ')
+                               off++;
+                       for (i = off; i < end && b[i] != '\0' &&
+                           b[i] != '\n' && b[i] != '\r' &&
+                           b[i] != ' ' && b[i] != '\t'; i++)
+                               path[i - off] = b[i];
+                       path[i - off] = '\0';
+                       archive_strcpy(&(xar->cur_file->script), path);
+               }
+       }
+#endif
+
+       if (xar->cur_file->data.compression == NONE) {
+               if (write_to_temp(a, buff, size) != ARCHIVE_OK)
+                       return (ARCHIVE_FATAL);
+       } else {
+               if (write_to_temp(a, xar->wbuff, size) != ARCHIVE_OK)
+                       return (ARCHIVE_FATAL);
+       }
+       xar->bytes_remaining -= rsize;
+       xar->cur_file->data.length += size;
+
+       return (rsize);
+}
+
+static int
+xar_finish_entry(struct archive_write *a)
+{
+       struct xar *xar;
+       struct file *file;
+       size_t s;
+       ssize_t w;
+
+       xar = (struct xar *)a->format_data;
+       if (xar->cur_file == NULL)
+               return (ARCHIVE_OK);
+
+       while (xar->bytes_remaining > 0) {
+               s = xar->bytes_remaining;
+               if (s > a->null_length)
+                       s = a->null_length;
+               w = xar_write_data(a, a->nulls, s);
+               if (w > 0)
+                       xar->bytes_remaining -= w;
+               else
+                       return (w);
+       }
+       file = xar->cur_file;
+       checksum_final(&(xar->e_sumwrk), &(file->data.e_sum));
+       checksum_final(&(xar->a_sumwrk), &(file->data.a_sum));
+       xar->cur_file = NULL;
+
+       return (ARCHIVE_OK);
+}
+
+static int
+xmlwrite_string_attr(struct archive_write *a, xmlTextWriterPtr writer,
+       const char *key, const char *value,
+       const char *attrkey, const char *attrvalue)
+{
+       int r;
+
+       r = xmlTextWriterStartElement(writer, BAD_CAST(key));
+       if (r < 0) {
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_MISC,
+                   "xmlTextWriterStartElement() failed: %d", r);
+               return (ARCHIVE_FATAL);
+       }
+       if (attrkey != NULL && attrvalue != NULL) {
+               r = xmlTextWriterWriteAttribute(writer,
+                   BAD_CAST(attrkey), BAD_CAST(attrvalue));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterWriteAttribute() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+       }
+       if (value != NULL) {
+               r = xmlTextWriterWriteString(writer, BAD_CAST(value));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterWriteString() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+       }
+       r = xmlTextWriterEndElement(writer);
+       if (r < 0) {
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_MISC,
+                   "xmlTextWriterEndElement() failed: %d", r);
+               return (ARCHIVE_FATAL);
+       }
+       return (ARCHIVE_OK);
+}
+
+static int
+xmlwrite_string(struct archive_write *a, xmlTextWriterPtr writer,
+       const char *key, const char *value)
+{
+       int r;
+
+       if (value == NULL)
+               return (ARCHIVE_OK);
+       
+       r = xmlTextWriterStartElement(writer, BAD_CAST(key));
+       if (r < 0) {
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_MISC,
+                   "xmlTextWriterStartElement() failed: %d", r);
+               return (ARCHIVE_FATAL);
+       }
+       if (value != NULL) {
+               r = xmlTextWriterWriteString(writer, BAD_CAST(value));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterWriteString() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+       }
+       r = xmlTextWriterEndElement(writer);
+       if (r < 0) {
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_MISC,
+                   "xmlTextWriterEndElement() failed: %d", r);
+               return (ARCHIVE_FATAL);
+       }
+       return (ARCHIVE_OK);
+}
+
+static int
+xmlwrite_fstring(struct archive_write *a, xmlTextWriterPtr writer,
+       const char *key, const char *fmt, ...)
+{
+       struct xar *xar;
+       va_list ap;
+
+       xar = (struct xar *)a->format_data;
+       va_start(ap, fmt);
+       archive_string_empty(&xar->vstr);
+       archive_string_vsprintf(&xar->vstr, fmt, ap);
+       va_end(ap);
+       return (xmlwrite_string(a, writer, key, xar->vstr.s));
+}
+
+static int
+xmlwrite_time(struct archive_write *a, xmlTextWriterPtr writer,
+       const char *key, time_t t, int z)
+{
+       char timestr[100];
+       struct tm tm;
+
+#if defined(HAVE_GMTIME_R)
+       gmtime_r(&t, &tm);
+#elif defined(HAVE__GMTIME64_S)
+       _gmtime64_s(&tm, &t);
+#else
+       memcpy(&tm, gmtime(&t), sizeof(tm));
+#endif
+       memset(&timestr, 0, sizeof(timestr));
+       /* Do not use %F and %T for portability. */
+       strftime(timestr, sizeof(timestr), "%Y-%m-%dT%H:%M:%S", &tm);
+       if (z)
+               strcat(timestr, "Z");
+       return (xmlwrite_string(a, writer, key, timestr));
+}
+
+static int
+xmlwrite_mode(struct archive_write *a, xmlTextWriterPtr writer,
+       const char *key, mode_t mode)
+{
+       char ms[5];
+
+       ms[0] = '0';
+       ms[1] = '0' + ((mode >> 6) & 07);
+       ms[2] = '0' + ((mode >> 3) & 07);
+       ms[3] = '0' + (mode & 07);
+       ms[4] = '\0';
+
+       return (xmlwrite_string(a, writer, key, ms));
+}
+
+static int
+xmlwrite_sum(struct archive_write *a, xmlTextWriterPtr writer,
+       const char *key, struct chksumval *sum)
+{
+       const char *algname;
+       int algsize;
+       char buff[MAX_SUM_SIZE*2 + 1];
+       char *p;
+       unsigned char *s;
+       int i, r;
+
+       if (sum->len > 0) {
+               algname = getalgname(sum->alg);
+               algsize = getalgsize(sum->alg);
+               if (algname != NULL) {
+                       const char *hex = "0123456789abcdef";
+                       p = buff;
+                       s = sum->val;
+                       for (i = 0; i < algsize; i++) {
+                               *p++ = hex[(*s >> 4)];
+                               *p++ = hex[(*s & 0x0f)];
+                               s++;
+                       }
+                       *p = '\0';
+                       r = xmlwrite_string_attr(a, writer,
+                           key, buff,
+                           "style", algname);
+                       if (r < 0)
+                               return (ARCHIVE_FATAL);
+               }
+       }
+       return (ARCHIVE_OK);
+}
+
+static int
+xmlwrite_heap(struct archive_write *a, xmlTextWriterPtr writer,
+       struct heap_data *heap)
+{
+       const char *encname;
+       int r;
+
+       r = xmlwrite_fstring(a, writer, "length", "%ju", heap->length);
+       if (r < 0)
+               return (ARCHIVE_FATAL);
+       r = xmlwrite_fstring(a, writer, "offset", "%ju", heap->temp_offset);
+       if (r < 0)
+               return (ARCHIVE_FATAL);
+       r = xmlwrite_fstring(a, writer, "size", "%ju", heap->size);
+       if (r < 0)
+               return (ARCHIVE_FATAL);
+       switch (heap->compression) {
+       case GZIP:
+               encname = "application/x-gzip"; break;
+       case BZIP2:
+               encname = "application/x-bzip2"; break;
+       case LZMA:
+               encname = "application/x-lzma"; break;
+       case XZ:
+               encname = "application/x-xz"; break;
+       default:
+               encname = "application/octet-stream"; break;
+       }
+       r = xmlwrite_string_attr(a, writer, "encoding", NULL,
+           "style", encname);
+       if (r < 0)
+               return (ARCHIVE_FATAL);
+       r = xmlwrite_sum(a, writer, "archived-checksum", &(heap->a_sum));
+       if (r < 0)
+               return (ARCHIVE_FATAL);
+       r = xmlwrite_sum(a, writer, "extracted-checksum", &(heap->e_sum));
+       if (r < 0)
+               return (ARCHIVE_FATAL);
+       return (ARCHIVE_OK);
+}
+
+/*
+ * xar utility records fflags as following xml elements:
+ *   <flags>
+ *     <UserNoDump/>
+ *     .....
+ *   </flags>
+ * or
+ *   <ext2>
+ *     <NoDump/>
+ *     .....
+ *   </ext2>
+ * If xar is running on BSD platform, records <flags>..</flags>;
+ * if xar is running on linux platform, records <ext2>..</ext2>;
+ * otherwise does not record.
+ *
+ * Our implements records both <flags> and <ext2> if it's necessary.
+ */
+static int
+make_fflags_entry(struct archive_write *a, xmlTextWriterPtr writer,
+    const char *element, const char *fflags_text)
+{
+       static const struct flagentry {
+               const char      *name;
+               const char      *xarname;
+       }
+       flagbsd[] = {
+               { "sappnd",     "SystemAppend"},
+               { "sappend",    "SystemAppend"},
+               { "arch",       "SystemArchived"},
+               { "archived",   "SystemArchived"},
+               { "schg",       "SystemImmutable"},
+               { "schange",    "SystemImmutable"},
+               { "simmutable", "SystemImmutable"},
+               { "nosunlnk",   "SystemNoUnlink"},
+               { "nosunlink",  "SystemNoUnlink"},
+               { "snapshot",   "SystemSnapshot"},
+               { "uappnd",     "UserAppend"},
+               { "uappend",    "UserAppend"},
+               { "uchg",       "UserImmutable"},
+               { "uchange",    "UserImmutable"},
+               { "uimmutable", "UserImmutable"},
+               { "nodump",     "UserNoDump"},
+               { "noopaque",   "UserOpaque"},
+               { "nouunlnk",   "UserNoUnlink"},
+               { "nouunlink",  "UserNoUnlink"},
+               { NULL, NULL}
+       },
+       flagext2[] = {
+               { "sappnd",     "AppendOnly"},
+               { "sappend",    "AppendOnly"},
+               { "schg",       "Immutable"},
+               { "schange",    "Immutable"},
+               { "simmutable", "Immutable"},
+               { "nodump",     "NoDump"},
+               { "nouunlnk",   "Undelete"},
+               { "nouunlink",  "Undelete"},
+               { "btree",      "BTree"},
+               { "comperr",    "CompError"},
+               { "compress",   "Compress"},
+               { "noatime",    "NoAtime"},
+               { "compdirty",  "CompDirty"},
+               { "comprblk",   "CompBlock"},
+               { "dirsync",    "DirSync"},
+               { "hashidx",    "HashIndexed"},
+               { "imagic",     "iMagic"},
+               { "journal",    "Journaled"},
+               { "securedeletion",     "SecureDeletion"},
+               { "sync",       "Synchronous"},
+               { "notail",     "NoTail"},
+               { "topdir",     "TopDir"},
+               { "reserved",   "Reserved"},
+               { NULL, NULL}
+       };
+       const struct flagentry *fe, *flagentry;
+#define FLAGENTRY_MAXSIZE ((sizeof(flagbsd)+sizeof(flagext2))/sizeof(flagbsd))
+       const struct flagentry *avail[FLAGENTRY_MAXSIZE];
+       const char *p;
+       int i, n, r;
+
+       if (strcmp(element, "ext2") == 0)
+               flagentry = flagext2;
+       else
+               flagentry = flagbsd;
+       n = 0;
+       p = fflags_text;
+       do {
+               const char *cp;
+
+               cp = strchr(p, ',');
+               if (cp == NULL)
+                       cp = p + strlen(p);
+
+               for (fe = flagentry; fe->name != NULL; fe++) {
+                       if (fe->name[cp - p] != '\0'
+                           || p[0] != fe->name[0])
+                               continue;
+                       if (strncmp(p, fe->name, cp - p) == 0) {
+                               avail[n++] = fe;
+                               break;
+                       }
+               }
+               if (*cp == ',')
+                       p = cp + 1;
+               else
+                       p = NULL;
+       } while (p != NULL);
+
+       if (n > 0) {
+               r = xmlTextWriterStartElement(writer, BAD_CAST(element));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterStartElement() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+               for (i = 0; i < n; i++) {
+                       r = xmlwrite_string(a, writer,
+                           avail[i]->xarname, NULL);
+                       if (r != ARCHIVE_OK)
+                               return (r);
+               }
+
+               r = xmlTextWriterEndElement(writer);
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterEndElement() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+       }
+       return (ARCHIVE_OK);
+}
+
+static int
+make_file_entry(struct archive_write *a, xmlTextWriterPtr writer,
+    struct file *file)
+{
+       struct xar *xar;
+       const char *filetype, *filelink, *fflags;
+       struct archive_string linkto;
+       struct heap_data *heap;
+       unsigned char *tmp;
+       int r, r2, l, ll;
+
+       xar = (struct xar *)a->format_data;
+       r2 = ARCHIVE_OK;
+
+       /*
+        * Make a file name entry, "<name>".
+        */
+       l = ll = archive_strlen(&(file->basename));
+       tmp = malloc(l);
+       if (tmp == NULL) {
+               archive_set_error(&a->archive, ENOMEM,
+                   "Can't allocate memory");
+               return (ARCHIVE_FATAL);
+       }
+       r = UTF8Toisolat1(tmp, &l, BAD_CAST(file->basename.s), &ll);
+       free(tmp);
+       if (r < 0) {
+               r = xmlTextWriterWriteAttribute(writer,
+                   BAD_CAST("enctype"), BAD_CAST("base64"));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterWriteAttribute() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+               r = xmlTextWriterWriteBase64(writer, file->basename.s,
+                   0, archive_strlen(&(file->basename)));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterWriteBase64() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+       } else {
+               r = xmlwrite_string(a, writer, "name", file->basename.s);
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+       }
+
+       /*
+        * Make a file type entry, "<type>".
+        */
+       filelink = NULL;
+       archive_string_init(&linkto);
+       switch (archive_entry_filetype(file->entry)) {
+       case AE_IFDIR:
+               filetype = "directory"; break;
+       case AE_IFLNK:
+               filetype = "symlink"; break;
+       case AE_IFCHR:
+               filetype = "character special"; break;
+       case AE_IFBLK:
+               filetype = "block special"; break;
+       case AE_IFSOCK:
+               filetype = "socket"; break;
+       case AE_IFIFO:
+               filetype = "fifo"; break;
+       case AE_IFREG:
+       default:
+               if (file->hardlink_target != NULL) {
+                       filetype = "hardlink";
+                       filelink = "link";
+                       if (file->hardlink_target == file)
+                               archive_strcpy(&linkto, "original");
+                       else
+                               archive_string_sprintf(&linkto, "%d",
+                                   file->hardlink_target->id);
+               } else
+                       filetype = "file";
+               break;
+       }
+       r = xmlwrite_string_attr(a, writer, "type", filetype,
+           filelink, linkto.s);
+       archive_string_free(&linkto);
+       if (r < 0)
+               return (ARCHIVE_FATAL);
+
+       /*
+        * On a virtual directory, we record "name" and "type" only.
+        */
+       if (file->virtual)
+               return (ARCHIVE_OK);
+
+       switch (archive_entry_filetype(file->entry)) {
+       case AE_IFLNK:
+               /*
+                * xar utility has checked a file type, which
+                * a symblic-link file has referenced.
+                * For example:
+                *   <link type="directory">../ref/</link>
+                *   The symlink target file is "../ref/" and its
+                *   file type is a directory.
+                *
+                *   <link type="file">../f</link>
+                *   The symlink target file is "../f" and its
+                *   file type is a regular file.
+                *
+                * But our implemention cannot do it, and then we
+                * always record that a attribute "type" is "borken",
+                * for example:
+                *   <link type="broken">foo/bar</link>
+                *   It means "foo/bar" is not reachable.
+                */
+               r = xmlwrite_string_attr(a, writer, "link",
+                   file->symlink.s,
+                   "type", "broken");
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+               break;
+       case AE_IFCHR:
+       case AE_IFBLK:
+               r = xmlTextWriterStartElement(writer, BAD_CAST("device"));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterStartElement() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+               r = xmlwrite_fstring(a, writer, "major",
+                   "%d", archive_entry_rdevmajor(file->entry));
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+               r = xmlwrite_fstring(a, writer, "minor",
+                   "%d", archive_entry_rdevminor(file->entry));
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+               r = xmlTextWriterEndElement(writer);
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterEndElement() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+               break;
+       default:
+               break;
+       }
+
+       /*
+        * Make a inode entry, "<inode>".
+        */
+       r = xmlwrite_fstring(a, writer, "inode",
+           "%jd", archive_entry_ino64(file->entry));
+       if (r < 0)
+               return (ARCHIVE_FATAL);
+       if (archive_entry_dev(file->entry) != 0) {
+               r = xmlwrite_fstring(a, writer, "deviceno",
+                   "%d", archive_entry_dev(file->entry));
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+       }
+
+       /*
+        * Make a file mode entry, "<mode>".
+        */
+       r = xmlwrite_mode(a, writer, "mode",
+           archive_entry_mode(file->entry));
+       if (r < 0)
+               return (ARCHIVE_FATAL);
+
+       /*
+        * Make a user entry, "<uid>" and "<user>.
+        */
+       r = xmlwrite_fstring(a, writer, "uid",
+           "%d", archive_entry_uid(file->entry));
+       if (r < 0)
+               return (ARCHIVE_FATAL);
+       if (archive_entry_uname(file->entry) != NULL) {
+               utf8_encode(&(xar->tstr),
+                   archive_entry_uname_w(file->entry));
+               if (archive_strlen(&(xar->tstr)) == 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_FILE_FORMAT,
+                           "Can't translate uname '%s' to UTF-8",
+                           archive_entry_uname(file->entry));
+                       r = xmlwrite_string(a, writer, "user",
+                           archive_entry_uname(file->entry));
+                       if (r < 0)
+                               return (ARCHIVE_FATAL);
+                       r2 = ARCHIVE_WARN;
+               } else {
+                       r = xmlwrite_string(a, writer, "user",
+                           xar->tstr.s);
+                       if (r < 0)
+                               return (ARCHIVE_FATAL);
+               }
+       }
+
+       /*
+        * Make a group entry, "<gid>" and "<group>.
+        */
+       r = xmlwrite_fstring(a, writer, "gid",
+           "%d", archive_entry_gid(file->entry));
+       if (r < 0)
+               return (ARCHIVE_FATAL);
+       if (archive_entry_gname(file->entry) != NULL) {
+               utf8_encode(&(xar->tstr),
+                   archive_entry_gname_w(file->entry));
+               if (archive_strlen(&(xar->tstr)) == 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_FILE_FORMAT,
+                           "Can't translate gname '%s' to UTF-8",
+                           archive_entry_gname(file->entry));
+                       r = xmlwrite_string(a, writer, "group",
+                           archive_entry_gname(file->entry));
+                       if (r < 0)
+                               return (ARCHIVE_FATAL);
+                       r2 = ARCHIVE_WARN;
+               } else {
+                       r = xmlwrite_string(a, writer, "group",
+                           xar->tstr.s);
+                       if (r < 0)
+                               return (ARCHIVE_FATAL);
+               }
+       }
+
+       /*
+        * Make a ctime entry, "<ctime>".
+        */
+       if (archive_entry_ctime_is_set(file->entry)) {
+               r = xmlwrite_time(a, writer, "ctime",
+                   archive_entry_ctime(file->entry), 1);
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+       }
+
+       /*
+        * Make a mtime entry, "<mtime>".
+        */
+       if (archive_entry_mtime_is_set(file->entry)) {
+               r = xmlwrite_time(a, writer, "mtime",
+                   archive_entry_mtime(file->entry), 1);
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+       }
+
+       /*
+        * Make a atime entry, "<atime>".
+        */
+       if (archive_entry_atime_is_set(file->entry)) {
+               r = xmlwrite_time(a, writer, "atime",
+                   archive_entry_atime(file->entry), 1);
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+       }
+
+       /*
+        * Make fflags entries, "<flags>" and "<ext2>".
+        */
+       fflags = archive_entry_fflags_text(file->entry);
+       if (fflags != NULL) {
+               r = make_fflags_entry(a, writer, "flags", fflags);
+               if (r < 0)
+                       return (r);
+               r = make_fflags_entry(a, writer, "ext2", fflags);
+               if (r < 0)
+                       return (r);
+       }
+
+       /*
+        * Make extended attribute entries, "<ea>".
+        */
+       archive_entry_xattr_reset(file->entry);
+       for (heap = file->xattr.first; heap != NULL; heap = heap->next) {
+               const char *name;
+               const void *value;
+               size_t size;
+
+               archive_entry_xattr_next(file->entry,
+                   &name, &value, &size);
+               r = xmlTextWriterStartElement(writer, BAD_CAST("ea"));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterStartElement() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+               r = xmlTextWriterWriteFormatAttribute(writer,
+                   BAD_CAST("id"), "%d", heap->id);
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterWriteAttribute() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+               r = xmlwrite_heap(a, writer, heap);
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+               r = xmlwrite_string(a, writer, "name", name);
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+
+               r = xmlTextWriterEndElement(writer);
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterEndElement() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+       }
+
+       /*
+        * Make a file data entry, "<data>".
+        */
+       if (file->data.length > 0) {
+               r = xmlTextWriterStartElement(writer, BAD_CAST("data"));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterStartElement() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+
+               r = xmlwrite_heap(a, writer, &(file->data));
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+
+               r = xmlTextWriterEndElement(writer);
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterEndElement() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+       }
+
+       if (archive_strlen(&file->script) > 0) {
+               r = xmlTextWriterStartElement(writer, BAD_CAST("content"));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterStartElement() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+
+               r = xmlwrite_string(a, writer,
+                   "interpreter", file->script.s);
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+
+               r = xmlwrite_string(a, writer, "type", "script");
+               if (r < 0)
+                       return (ARCHIVE_FATAL);
+
+               r = xmlTextWriterEndElement(writer);
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterEndElement() failed: %d", r);
+                       return (ARCHIVE_FATAL);
+               }
+       }
+
+       return (r2);
+}
+
+/*
+ * Make the TOC
+ */
+static int
+make_toc(struct archive_write *a)
+{
+       struct xar *xar;
+       struct file *np;
+       xmlBufferPtr bp;
+       xmlTextWriterPtr writer;
+       int algsize;
+       int r, ret;
+
+       xar = (struct xar *)a->format_data;
+
+       ret = ARCHIVE_FATAL;
+
+       /*
+        * Initialize xml writer.
+        */
+       writer = NULL;
+       bp = xmlBufferCreate();
+       if (bp == NULL) {
+               archive_set_error(&a->archive, ENOMEM,
+                   "xmlBufferCreate() "
+                   "couldn't create xml buffer");
+               goto exit_toc;
+       }
+       writer = xmlNewTextWriterMemory(bp, 0);
+       if (writer == NULL) {
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_MISC,
+                   "xmlNewTextWriterMemory() "
+                   "couldn't create xml writer");
+               goto exit_toc;
+       }
+       r = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL);
+       if (r < 0) {
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_MISC,
+                   "xmlTextWriterStartDocument() failed: %d", r);
+               goto exit_toc;
+       }
+       r = xmlTextWriterSetIndent(writer, 4);
+       if (r < 0) {
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_MISC,
+                   "xmlTextWriterSetIndent() failed: %d", r);
+               goto exit_toc;
+       }
+
+       /*
+        * Start recoding TOC
+        */
+       r = xmlTextWriterStartElement(writer, BAD_CAST("xar"));
+       if (r < 0) {
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_MISC,
+                   "xmlTextWriterStartElement() failed: %d", r);
+               goto exit_toc;
+       }
+       r = xmlTextWriterStartElement(writer, BAD_CAST("toc"));
+       if (r < 0) {
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_MISC,
+                   "xmlTextWriterStartDocument() failed: %d", r);
+               goto exit_toc;
+       }
+
+       /*
+        * Record the creation time of the archive file.
+        */
+       r = xmlwrite_time(a, writer, "creation-time", time(NULL), 0);
+       if (r < 0)
+               goto exit_toc;
+
+       /*
+        * Record the checksum value of TOC
+        */
+       algsize = getalgsize(xar->opt_toc_sumalg);
+       if (algsize) {
+               /*
+                * Record TOC checksum
+                */
+               r = xmlTextWriterStartElement(writer, BAD_CAST("checksum"));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterStartElement() failed: %d", r);
+                       goto exit_toc;
+               }
+               r = xmlTextWriterWriteAttribute(writer,
+                   BAD_CAST("style"), BAD_CAST(getalgname(xar->opt_toc_sumalg)));
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterWriteAttribute() failed: %d", r);
+                       goto exit_toc;
+               }
+
+               /*
+                * Record the offset of the value of checksum of TOC
+                */
+               r = xmlwrite_string(a, writer, "offset", "0");
+               if (r < 0)
+                       goto exit_toc;
+
+               /*
+                * Record the size of the value of checksum of TOC
+                */
+               r = xmlwrite_fstring(a, writer, "size", "%d", algsize);
+               if (r < 0)
+                       goto exit_toc;
+
+               r = xmlTextWriterEndElement(writer);
+               if (r < 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "xmlTextWriterEndElement() failed: %d", r);
+                       goto exit_toc;
+               }
+       }
+
+       np = xar->root;
+       do {
+               if (np != np->parent) {
+                       r = make_file_entry(a, writer, np);
+                       if (r != ARCHIVE_OK)
+                               goto exit_toc;
+               }
+
+               if (np->dir && np->children.first != NULL) {
+                       /* Enter to sub directories. */
+                       np = np->children.first;
+                       r = xmlTextWriterStartElement(writer,
+                           BAD_CAST("file"));
+                       if (r < 0) {
+                               archive_set_error(&a->archive,
+                                   ARCHIVE_ERRNO_MISC,
+                                   "xmlTextWriterStartElement() "
+                                   "failed: %d", r);
+                               goto exit_toc;
+                       }
+                       r = xmlTextWriterWriteFormatAttribute(
+                           writer, BAD_CAST("id"), "%d", np->id);
+                       if (r < 0) {
+                               archive_set_error(&a->archive,
+                                   ARCHIVE_ERRNO_MISC,
+                                   "xmlTextWriterWriteAttribute() "
+                                   "failed: %d", r);
+                               goto exit_toc;
+                       }
+                       continue;
+               }
+               while (np != np->parent) {
+                       r = xmlTextWriterEndElement(writer);
+                       if (r < 0) {
+                               archive_set_error(&a->archive,
+                                   ARCHIVE_ERRNO_MISC,
+                                   "xmlTextWriterEndElement() "
+                                   "failed: %d", r);
+                               goto exit_toc;
+                       }
+                       if (np->chnext == NULL) {
+                               /* Return to the parent directory. */
+                               np = np->parent;
+                       } else {
+                               np = np->chnext;
+                               r = xmlTextWriterStartElement(writer,
+                                   BAD_CAST("file"));
+                               if (r < 0) {
+                                       archive_set_error(&a->archive,
+                                           ARCHIVE_ERRNO_MISC,
+                                           "xmlTextWriterStartElement() "
+                                           "failed: %d", r);
+                                       goto exit_toc;
+                               }
+                               r = xmlTextWriterWriteFormatAttribute(
+                                   writer, BAD_CAST("id"), "%d", np->id);
+                               if (r < 0) {
+                                       archive_set_error(&a->archive,
+                                           ARCHIVE_ERRNO_MISC,
+                                           "xmlTextWriterWriteAttribute() "
+                                           "failed: %d", r);
+                                       goto exit_toc;
+                               }
+                               break;
+                       }
+               }
+       } while (np != np->parent);
+
+       r = xmlTextWriterEndDocument(writer);
+       if (r < 0) {
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_MISC,
+                   "xmlTextWriterEndDocument() failed: %d", r);
+               goto exit_toc;
+       }
+#if DEBUG_PRINT_TOC
+       fprintf(stderr, "\n---TOC-- %d bytes --\n%s\n",
+           strlen((const char *)bp->content), bp->content);
+#endif
+
+       /*
+        * Compress the TOC and calculate the sum of the TOC.
+        */
+       xar->toc.temp_offset = xar->temp_offset;
+       xar->toc.size = bp->use;
+       checksum_init(&(xar->a_sumwrk), xar->opt_toc_sumalg);
+
+       r = compression_init_encoder_gzip(&(a->archive),
+           &(xar->stream), 6, 1);
+       if (r != ARCHIVE_OK)
+               goto exit_toc;
+       xar->stream.next_in = bp->content;
+       xar->stream.avail_in = bp->use;
+       xar->stream.total_in = 0;
+       xar->stream.next_out = xar->wbuff;
+       xar->stream.avail_out = sizeof(xar->wbuff);
+       xar->stream.total_out = 0;
+       for (;;) {
+               size_t size;
+
+               r = compression_code(&(a->archive),
+                   &(xar->stream), ARCHIVE_Z_FINISH);
+               if (r != ARCHIVE_OK && r != ARCHIVE_EOF)
+                       goto exit_toc;
+               size = sizeof(xar->wbuff) - xar->stream.avail_out;
+               checksum_update(&(xar->a_sumwrk), xar->wbuff, size);
+               if (write_to_temp(a, xar->wbuff, size) != ARCHIVE_OK)
+                       goto exit_toc;
+               if (r == ARCHIVE_EOF)
+                       break;
+               xar->stream.next_out = xar->wbuff;
+               xar->stream.avail_out = sizeof(xar->wbuff);
+       }
+       r = compression_end(&(a->archive), &(xar->stream));
+       if (r != ARCHIVE_OK)
+               goto exit_toc;
+       xar->toc.length = xar->stream.total_out;
+       xar->toc.compression = GZIP;
+       checksum_final(&(xar->a_sumwrk), &(xar->toc.a_sum));
+
+       ret = ARCHIVE_OK;
+exit_toc:
+       if (writer)
+               xmlFreeTextWriter(writer);
+       if (bp)
+               xmlBufferFree(bp);
+
+       return (ret);
+}
+
+static int
+flush_wbuff(struct archive_write *a)
+{
+       struct xar *xar;
+       int r;
+       size_t s;
+
+       xar = (struct xar *)a->format_data;
+       s = sizeof(xar->wbuff) - xar->wbuff_remaining;
+       r = __archive_write_output(a, xar->wbuff, s);
+       if (r != ARCHIVE_OK)
+               return (r);
+       xar->wbuff_remaining = sizeof(xar->wbuff);
+       return (r);
+}
+
+static int
+copy_out(struct archive_write *a, uint64_t offset, uint64_t length)
+{
+       struct xar *xar;
+       int r;
+
+       xar = (struct xar *)a->format_data;
+       if (lseek(xar->temp_fd, offset, SEEK_SET) < 0) {
+               archive_set_error(&(a->archive), errno, "lseek failed");
+               return (ARCHIVE_FATAL);
+       }
+       while (length) {
+               size_t rsize;
+               ssize_t rs;
+               unsigned char *wb;
+
+               if (length > xar->wbuff_remaining)
+                       rsize = xar->wbuff_remaining;
+               else
+                       rsize = (size_t)length;
+               wb = xar->wbuff + (sizeof(xar->wbuff) - xar->wbuff_remaining);
+               rs = read(xar->temp_fd, wb, rsize);
+               if (rs < 0) {
+                       archive_set_error(&(a->archive), errno,
+                           "Can't read temporary file(%zd)",
+                           rs);
+                       return (ARCHIVE_FATAL);
+               }
+               xar->wbuff_remaining -= rs;
+               length -= rs;
+               if (xar->wbuff_remaining == 0) {
+                       r = flush_wbuff(a);
+                       if (r != ARCHIVE_OK)
+                               return (r);
+               }
+       }
+       return (ARCHIVE_OK);
+}
+
+static int
+xar_close(struct archive_write *a)
+{
+       struct xar *xar;
+       unsigned char *wb;
+       uint64_t length;
+       int r;
+
+       xar = (struct xar *)a->format_data;
+
+       /* Empty! */
+       if (xar->root->children.first == NULL)
+               return (ARCHIVE_OK);
+
+       /* Save the length of all file extended attributes and contents. */
+       length = xar->temp_offset;
+
+       /* Connect hardlinked files */
+       file_connect_hardlink_files(xar);
+
+       /* Make the TOC */
+       r = make_toc(a);
+       if (r != ARCHIVE_OK)
+               return (r);
+       /*
+        * Make the xar header on wbuff(write buffer).
+        */
+       wb = xar->wbuff;
+       xar->wbuff_remaining = sizeof(xar->wbuff);
+       archive_be32enc(&wb[0], HEADER_MAGIC);
+       archive_be16enc(&wb[4], HEADER_SIZE);
+       archive_be16enc(&wb[6], HEADER_VERSION);
+       archive_be64enc(&wb[8], xar->toc.length);
+       archive_be64enc(&wb[16], xar->toc.size);
+       archive_be32enc(&wb[24], xar->toc.a_sum.alg);
+       xar->wbuff_remaining -= HEADER_SIZE;
+
+       /*
+        * Write the TOC
+        */
+       r = copy_out(a, xar->toc.temp_offset, xar->toc.length);
+       if (r != ARCHIVE_OK)
+               return (r);
+
+       /* Write the checksum value of the TOC. */
+       if (xar->toc.a_sum.len) {
+               if (xar->wbuff_remaining < xar->toc.a_sum.len) {
+                       r = flush_wbuff(a);
+                       if (r != ARCHIVE_OK)
+                               return (r);
+               }
+               wb = xar->wbuff + (sizeof(xar->wbuff) - xar->wbuff_remaining);
+               memcpy(wb, xar->toc.a_sum.val, xar->toc.a_sum.len);
+               xar->wbuff_remaining -= xar->toc.a_sum.len;
+       }
+
+       /*
+        * Write all file extended attributes and contents.
+        */
+       r = copy_out(a, xar->toc.a_sum.len, length);
+       if (r != ARCHIVE_OK)
+               return (r);
+       r = flush_wbuff(a);
+       return (r);
+}
+
+static int
+xar_free(struct archive_write *a)
+{
+       struct xar *xar;
+
+       xar = (struct xar *)a->format_data;
+       archive_string_free(&(xar->cur_dirstr));
+       archive_string_free(&(xar->tstr));
+       archive_string_free(&(xar->vstr));
+       file_free_hardlinks(xar);
+       file_free_register(xar);
+       compression_end(&(a->archive), &(xar->stream));
+       free(xar);
+
+       return (ARCHIVE_OK);
+}
+
+static int
+file_cmp_node(const struct archive_rb_node *n1,
+    const struct archive_rb_node *n2)
+{
+       struct file *f1 = (struct file *)n1;
+       struct file *f2 = (struct file *)n2;
+
+       return (strcmp(f1->basename.s, f2->basename.s));
+}
+        
+static int
+file_cmp_key(const struct archive_rb_node *n, const void *key)
+{
+       struct file *f = (struct file *)n;
+
+       return (strcmp(f->basename.s, (const char *)key));
+}
+
+static struct file *
+file_new(struct archive_entry *entry)
+{
+       struct file *file;
+       static const struct archive_rb_tree_ops rb_ops = {
+               file_cmp_node, file_cmp_key
+       };
+
+       file = calloc(1, sizeof(*file));
+       if (file == NULL)
+               return (NULL);
+
+       if (entry != NULL)
+               file->entry = archive_entry_clone(entry);
+       else
+               file->entry = archive_entry_new();
+       if (file->entry == NULL) {
+               free(file);
+               return (NULL);
+       }
+       __archive_rb_tree_init(&(file->rbtree), &rb_ops);
+       file->children.first = NULL;
+       file->children.last = &(file->children.first);
+       file->xattr.first = NULL;
+       file->xattr.last = &(file->xattr.first);
+       archive_string_init(&(file->parentdir));
+       archive_string_init(&(file->basename));
+       archive_string_init(&(file->symlink));
+       archive_string_init(&(file->script));
+       if (entry != NULL && archive_entry_filetype(entry) == AE_IFDIR)
+               file->dir = 1;
+
+       return (file);
+}
+
+static void
+file_free(struct file *file)
+{
+       struct heap_data *heap, *next_heap;
+
+       heap = file->xattr.first;
+       while (heap != NULL) {
+               next_heap = heap->next;
+               free(heap);
+               heap = next_heap;
+       }
+       archive_string_free(&(file->parentdir));
+       archive_string_free(&(file->basename));
+       archive_string_free(&(file->symlink));
+       archive_string_free(&(file->script));
+       free(file);
+}
+
+static struct file *
+file_create_virtual_dir(struct xar *xar, const char *pathname)
+{
+       struct file *file;
+
+       file = file_new(NULL);
+       if (file == NULL)
+               return (NULL);
+       archive_entry_set_pathname(file->entry, pathname);
+       archive_entry_set_mode(file->entry, 0555 | AE_IFDIR);
+
+       file->dir = 1;
+       file->virtual = 1;
+
+       return (file);
+}
+
+static int
+file_add_child_tail(struct file *parent, struct file *child)
+{
+       if (!__archive_rb_tree_insert_node(
+           &(parent->rbtree), (struct archive_rb_node *)child))
+               return (0);
+       child->chnext = NULL;
+       *parent->children.last = child;
+       parent->children.last = &(child->chnext);
+       child->parent = parent;
+       return (1);
+}
+
+/*
+ * Find a entry from `parent'
+ */
+static struct file *
+file_find_child(struct file *parent, const char *child_name)
+{
+        struct file *np;
+
+        np = (struct file *)__archive_rb_tree_find_node(
+            &(parent->rbtree), child_name);
+        return (np);
+}
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+static void
+cleanup_backslash(char *utf8, size_t len)
+{
+
+       /* Convert a path-separator from '\' to  '/' */
+        while (*utf8 != '\0' && len) {
+                if (*utf8 == '\\')
+                        *utf8 = '/';
+               ++utf8;
+                --len;
+        }
+}
+#else
+#define cleanup_backslash(p, len)      /* nop */
+#endif
+
+/*
+ * Generate a parent directory name and a base name from a pathname.
+ */
+static int
+file_gen_utility_names(struct archive_write *a, struct file *file)
+{
+       struct xar *xar;
+       char *p, *dirname, *slash;
+       size_t len;
+       int r = ARCHIVE_OK;
+
+       xar = (struct xar *)a->format_data;
+       archive_string_empty(&(file->parentdir));
+       archive_string_empty(&(file->basename));
+       archive_string_empty(&(file->symlink));
+
+       if (file->parent == file)/* virtual root */
+               return (ARCHIVE_OK);
+
+       utf8_encode(&(xar->tstr), archive_entry_pathname_w(file->entry));
+       if (archive_strlen(&(xar->tstr)) == 0) {
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_FILE_FORMAT,
+                   "Can't translate pathname '%s' to UTF-8",
+                   archive_entry_pathname(file->entry));
+               archive_strcpy(&(file->parentdir),
+                   archive_entry_pathname(file->entry));
+               r = ARCHIVE_WARN;
+       } else
+               archive_string_copy(&(file->parentdir), &(xar->tstr));
+       len = file->parentdir.length;
+       p = dirname = file->parentdir.s;
+       /*
+        * Convert a path-separator from '\' to  '/'
+        */
+       cleanup_backslash(p, len);
+
+       if (p[0] == '/') {
+               p++;
+               len--;
+       }
+       /*
+        * Remove leading '../' and './' elements
+        */
+       while (*p) {
+               if (p[0] != '.')
+                       break;
+               if (p[1] == '.' && p[2] == '/') {
+                       p += 3;
+                       len -= 3;
+               } else if (p[1] == '/') {
+                       p += 2;
+                       len -= 2;
+               } 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);
+
+       if (archive_entry_filetype(file->entry) == AE_IFLNK) {
+               /* Convert symlink name too. */
+               utf8_encode(&(xar->tstr),
+                   archive_entry_symlink_w(file->entry));
+               if (archive_strlen(&(xar->tstr)) == 0) {
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_FILE_FORMAT,
+                           "Can't translate symlink '%s' to UTF-8",
+                           archive_entry_symlink(file->entry));
+                       archive_strcpy(&(file->symlink),
+                           archive_entry_symlink(file->entry));
+                       r = ARCHIVE_WARN;
+               } else
+                       archive_string_copy(&(file->symlink), &(xar->tstr));
+               cleanup_backslash(file->symlink.s, file->symlink.length);
+       }
+       /*
+        * - Count up directory elements.
+        * - Find out the position which points the last position of
+        *   path separator('/').
+        */
+       slash = NULL;
+       for (; *p != '\0'; p++)
+               if (*p == '/')
+                       slash = p;
+       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 (r);
+       }
+
+       /* Make a basename from dirname and slash */
+       *slash  = '\0';
+       file->parentdir.length = slash - dirname;
+       archive_strcpy(&(file->basename),  slash + 1);
+       return (r);
+}
+
+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
+file_tree(struct archive_write *a, struct file **filepp)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       char name[_MAX_FNAME];/* Included null terminator size. */
+#else
+       char name[NAME_MAX+1];
+#endif
+       struct xar *xar = (struct xar *)a->format_data;
+       struct file *dent, *file, *np;
+       const char *fn, *p;
+       int l;
+
+       file = *filepp;
+       dent = xar->root;
+       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 isoent to
+        * `cur_dirent'.
+        */
+       if (archive_strlen(&(xar->cur_dirstr))
+             == archive_strlen(&(file->parentdir)) &&
+           strcmp(xar->cur_dirstr.s, fn) == 0) {
+               if (!file_add_child_tail(xar->cur_dirent, file)) {
+                       np = (struct file *)__archive_rb_tree_find_node(
+                           &(xar->cur_dirent->rbtree),
+                           file->basename.s);
+                       goto same_entry;
+               }
+               return (ARCHIVE_OK);
+       }
+
+       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");
+                       file_free(file);
+                       *filepp = NULL;
+                       return (ARCHIVE_FATAL);
+               }
+
+               np = file_find_child(dent, name);
+               if (np == NULL || fn[0] == '\0')
+                       break;
+
+               /* Find next subdirectory. */
+               if (!np->dir) {
+                       /* NOT Directory! */
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_MISC,
+                           "`%s' is not directory, we cannot insert `%s' ",
+                           archive_entry_pathname(np->entry),
+                           archive_entry_pathname(file->entry));
+                       file_free(file);
+                       *filepp = NULL;
+                       return (ARCHIVE_FAILED);
+               }
+               fn += l;
+               if (fn[0] == '/')
+                       fn++;
+               dent = np;
+       }
+       if (np == NULL) {
+               /*
+                * Create virtual parent directories.
+                */
+               while (fn[0] != '\0') {
+                       struct file *vp;
+                       struct archive_string as;
+
+                       archive_string_init(&as);
+                       __archive_string_append(&as, p, fn - p + l);
+                       if (as.s[as.length-1] == '/') {
+                               as.s[as.length-1] = '\0';
+                               as.length--;
+                       }
+                       vp = file_create_virtual_dir(xar, as.s);
+                       if (vp == NULL) {
+                               archive_string_free(&as);
+                               archive_set_error(&a->archive, ENOMEM,
+                                   "Can't allocate memory");
+                               file_free(file);
+                               *filepp = NULL;
+                               return (ARCHIVE_FATAL);
+                       }
+                       archive_string_free(&as);
+                       file_gen_utility_names(a, vp);
+                       file_add_child_tail(dent, vp);
+                       file_register(xar, 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");
+                               file_free(file);
+                               *filepp = NULL;
+                               return (ARCHIVE_FATAL);
+                       }
+                       dent = np;
+               }
+
+               /* Found out the parent directory where isoent can be
+                * inserted. */
+               xar->cur_dirent = dent;
+               archive_string_empty(&(xar->cur_dirstr));
+               archive_string_ensure(&(xar->cur_dirstr),
+                   archive_strlen(&(dent->parentdir)) +
+                   archive_strlen(&(dent->basename)) + 2);
+               if (archive_strlen(&(dent->parentdir)) +
+                   archive_strlen(&(dent->basename)) == 0)
+                       xar->cur_dirstr.s[0] = 0;
+               else {
+                       if (archive_strlen(&(dent->parentdir)) > 0) {
+                               archive_string_copy(&(xar->cur_dirstr),
+                                   &(dent->parentdir));
+                               archive_strappend_char(&(xar->cur_dirstr), '/');
+                       }
+                       archive_string_concat(&(xar->cur_dirstr),
+                           &(dent->basename));
+               }
+
+               if (!file_add_child_tail(dent, file)) {
+                       np = (struct file *)__archive_rb_tree_find_node(
+                           &(dent->rbtree), file->basename.s);
+                       goto same_entry;
+               }
+               return (ARCHIVE_OK);
+       }
+
+same_entry:
+       /*
+        * We have already has the entry the filename of which is
+        * the same.
+        */
+       if (archive_entry_filetype(np->entry) !=
+           archive_entry_filetype(file->entry)) {
+               archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                   "Found duplicate entries `%s' and its file type is "
+                   "different",
+                   archive_entry_pathname(np->entry));
+               file_free(file);
+               *filepp = NULL;
+               return (ARCHIVE_FAILED);
+       }
+       if (archive_entry_mtime(np->entry) <
+           archive_entry_mtime(file->entry) || np->virtual) {
+               /* Swap files. */
+               struct archive_entry *ent;
+
+               ent = np->entry;
+               np->entry = file->entry;
+               file->entry = ent;
+               np->virtual = 0;
+       }
+       file_free(file);
+       *filepp = np;
+       return (ARCHIVE_OK);
+}
+
+static void
+file_register(struct xar *xar, struct file *file)
+{
+       file->id = xar->file_idx++;
+        file->next = NULL;
+        *xar->file_list.last = file;
+        xar->file_list.last = &(file->next);
+}
+
+static void
+file_init_register(struct xar *xar)
+{
+       xar->file_list.first = NULL;
+       xar->file_list.last = &(xar->file_list.first);
+}
+
+static void
+file_free_register(struct xar *xar)
+{
+       struct file *file, *file_next;
+
+       file = xar->file_list.first;
+       while (file != NULL) {
+               file_next = file->next;
+               file_free(file);
+               file = file_next;
+       }
+}
+
+/*
+ * Register entry to get a hardlink target.
+ */
+static int
+file_register_hardlink(struct archive_write *a, struct file *file)
+{
+       struct xar *xar = (struct xar *)a->format_data;
+       struct hardlink *hl;
+       const char *pathname;
+
+       archive_entry_set_nlink(file->entry, 1);
+       pathname = archive_entry_hardlink(file->entry);
+       if (pathname == NULL) {
+               /* This `file` is a hardlink target. */
+               hl = malloc(sizeof(*hl));
+               if (hl == NULL) {
+                       archive_set_error(&a->archive, ENOMEM,
+                           "Can't allocate memory");
+                       return (ARCHIVE_FATAL);
+               }
+               hl->nlink = 1;
+               /* A hardlink target must be the first position. */
+               file->hlnext = NULL;
+               hl->file_list.first = file;
+               hl->file_list.last = &(file->hlnext);
+               __archive_rb_tree_insert_node(&(xar->hardlink_rbtree),
+                   (struct archive_rb_node *)hl);
+       } else {
+               hl = (struct hardlink *)__archive_rb_tree_find_node(
+                   &(xar->hardlink_rbtree), pathname);
+               if (hl != NULL) {
+                       /* Insert `file` entry into the tail. */
+                       file->hlnext = NULL;
+                       *hl->file_list.last = file;
+                       hl->file_list.last = &(file->hlnext);
+                       hl->nlink++;
+               }
+               archive_entry_unset_size(file->entry);
+       }
+
+       return (ARCHIVE_OK);
+}
+
+/*
+ * Hardlinked files have to have the same location of extent.
+ * We have to find out hardlink target entries for entries which
+ * have a hardlink target name.
+ */
+static void
+file_connect_hardlink_files(struct xar *xar)
+{
+       struct archive_rb_node *n;
+       struct hardlink *hl;
+       struct file *target, *nf;
+
+       ARCHIVE_RB_TREE_FOREACH(n, &(xar->hardlink_rbtree)) {
+               hl = (struct hardlink *)n;
+
+               /* The first entry must be a hardlink target. */
+               target = hl->file_list.first;
+               archive_entry_set_nlink(target->entry, hl->nlink);
+               if (hl->nlink > 1)
+                       /* It means this file is a hardlink
+                        * targe itself. */
+                       target->hardlink_target = target;
+               for (nf = target->hlnext;
+                   nf != NULL; nf = nf->hlnext) {
+                       nf->hardlink_target = target;
+                       archive_entry_set_nlink(nf->entry, hl->nlink);
+               }
+       }
+}
+
+static int
+file_hd_cmp_node(const struct archive_rb_node *n1,
+    const struct archive_rb_node *n2)
+{
+       struct hardlink *h1 = (struct hardlink *)n1;
+       struct hardlink *h2 = (struct hardlink *)n2;
+
+       return (strcmp(archive_entry_pathname(h1->file_list.first->entry),
+                      archive_entry_pathname(h2->file_list.first->entry)));
+}
+
+static int
+file_hd_cmp_key(const struct archive_rb_node *n, const void *key)
+{
+       struct hardlink *h = (struct hardlink *)n;
+
+       return (strcmp(archive_entry_pathname(h->file_list.first->entry),
+                      (const char *)key));
+}
+
+
+static void
+file_init_hardlinks(struct xar *xar)
+{
+       static const struct archive_rb_tree_ops rb_ops = {
+               file_hd_cmp_node, file_hd_cmp_key,
+       };
+       __archive_rb_tree_init(&(xar->hardlink_rbtree), &rb_ops);
+}
+
+static void
+file_free_hardlinks(struct xar *xar)
+{
+       struct archive_rb_node *n, *next;
+
+       for (n = ARCHIVE_RB_TREE_MIN(&(xar->hardlink_rbtree)); n;) {
+               next = __archive_rb_tree_iterate(&(xar->hardlink_rbtree),
+                   n, ARCHIVE_RB_DIR_RIGHT);
+               free(n);
+               n = next;
+       }
+}
+
+static void
+checksum_init(struct chksumwork *sumwrk, enum sumalg sum_alg)
+{
+       sumwrk->alg = sum_alg;
+       switch (sum_alg) {
+       case CKSUM_NONE:
+               break;
+       case CKSUM_SHA1:
+               archive_sha1_init(&(sumwrk->sha1ctx));
+               break;
+       case CKSUM_MD5:
+               archive_md5_init(&(sumwrk->md5ctx));
+               break;
+       }
+}
+
+static void
+checksum_update(struct chksumwork *sumwrk, const void *buff, size_t size)
+{
+
+       switch (sumwrk->alg) {
+       case CKSUM_NONE:
+               break;
+       case CKSUM_SHA1:
+               archive_sha1_update(&(sumwrk->sha1ctx), buff, size);
+               break;
+       case CKSUM_MD5:
+               archive_md5_update(&(sumwrk->md5ctx), buff, size);
+               break;
+       }
+}
+
+static void
+checksum_final(struct chksumwork *sumwrk, struct chksumval *sumval)
+{
+
+       switch (sumwrk->alg) {
+       case CKSUM_NONE:
+               sumval->len = 0;
+               break;
+       case CKSUM_SHA1:
+               archive_sha1_final(&(sumwrk->sha1ctx), sumval->val);
+               sumval->len = SHA1_SIZE;
+               break;
+       case CKSUM_MD5:
+               archive_md5_final(&(sumwrk->md5ctx), sumval->val);
+               sumval->len = MD5_SIZE;
+               break;
+       }
+       sumval->alg = sumwrk->alg;
+}
+
+#if !defined(HAVE_BZLIB_H) || !defined(HAVE_LZMA_H)
+static int
+compression_unsupported_encoder(struct archive *a,
+    struct la_zstream *lastrm, const char *name)
+{
+
+       archive_set_error(a, ARCHIVE_ERRNO_MISC,
+           "%s compression not supported on this platform", name);
+       lastrm->valid = 0;
+       lastrm->real_stream = NULL;
+       return (ARCHIVE_FAILED);
+}
+#endif
+
+static int
+compression_init_encoder_gzip(struct archive *a,
+    struct la_zstream *lastrm, int level, int withheader)
+{
+       z_stream *strm;
+
+       if (lastrm->valid)
+               compression_end(a, lastrm);
+       strm = calloc(1, sizeof(*strm));
+       if (strm == NULL) {
+               archive_set_error(a, ENOMEM,
+                   "Can't allocate memory for gzip stream");
+               return (ARCHIVE_FATAL);
+       }
+       /* zlib.h is not const-correct, so we need this one bit
+        * of ugly hackery to convert a const * pointer to
+        * a non-const pointer. */
+       strm->next_in = (Bytef *)(uintptr_t)(const void *)lastrm->next_in;
+       strm->avail_in = lastrm->avail_in;
+       strm->total_in = lastrm->total_in;
+       strm->next_out = lastrm->next_out;
+       strm->avail_out = lastrm->avail_out;
+       strm->total_out = lastrm->total_out;
+       if (deflateInit2(strm, level, Z_DEFLATED,
+           (withheader)?15:-15,
+           8, Z_DEFAULT_STRATEGY) != Z_OK) {
+               free(strm);
+               lastrm->real_stream = NULL;
+               archive_set_error(a, ARCHIVE_ERRNO_MISC,
+                   "Internal error initializing compression library");
+               return (ARCHIVE_FATAL);
+       }
+       lastrm->real_stream = strm;
+       lastrm->valid = 1;
+       lastrm->code = compression_code_gzip;
+       lastrm->end = compression_end_gzip;
+       return (ARCHIVE_OK);
+}
+
+static int
+compression_code_gzip(struct archive *a,
+    struct la_zstream *lastrm, enum la_zaction action)
+{
+       z_stream *strm;
+       int r;
+
+       strm = (z_stream *)lastrm->real_stream;
+       /* zlib.h is not const-correct, so we need this one bit
+        * of ugly hackery to convert a const * pointer to
+        * a non-const pointer. */
+       strm->next_in = (Bytef *)(uintptr_t)(const void *)lastrm->next_in;
+       strm->avail_in = lastrm->avail_in;
+       strm->total_in = lastrm->total_in;
+       strm->next_out = lastrm->next_out;
+       strm->avail_out = lastrm->avail_out;
+       strm->total_out = lastrm->total_out;
+       r = deflate(strm,
+           (action == ARCHIVE_Z_FINISH)? Z_FINISH: Z_NO_FLUSH);
+       lastrm->next_in = strm->next_in;
+       lastrm->avail_in = strm->avail_in;
+       lastrm->total_in = strm->total_in;
+       lastrm->next_out = strm->next_out;
+       lastrm->avail_out = strm->avail_out;
+       lastrm->total_out = strm->total_out;
+       switch (r) {
+       case Z_OK:
+               return (ARCHIVE_OK);
+       case Z_STREAM_END:
+               return (ARCHIVE_EOF);
+       default:
+               archive_set_error(a, ARCHIVE_ERRNO_MISC,
+                   "GZip compression failed:"
+                   " deflate() call returned status %d", r);
+               return (ARCHIVE_FATAL);
+       }
+}
+
+static int
+compression_end_gzip(struct archive *a, struct la_zstream *lastrm)
+{
+       z_stream *strm;
+       int r;
+
+       strm = (z_stream *)lastrm->real_stream;
+       r = deflateEnd(strm);
+       free(strm);
+       lastrm->real_stream = NULL;
+       lastrm->valid = 0;
+       if (r != Z_OK) {
+               archive_set_error(a, ARCHIVE_ERRNO_MISC,
+                   "Failed to clean up compressor");
+               return (ARCHIVE_FATAL);
+       }
+       return (ARCHIVE_OK);
+}
+
+#if defined(HAVE_BZLIB_H)
+static int
+compression_init_encoder_bzip2(struct archive *a,
+    struct la_zstream *lastrm, int level)
+{
+       bz_stream *strm;
+
+       if (lastrm->valid)
+               compression_end(a, lastrm);
+       strm = calloc(1, sizeof(*strm));
+       if (strm == NULL) {
+               archive_set_error(a, ENOMEM,
+                   "Can't allocate memory for bzip2 stream");
+               return (ARCHIVE_FATAL);
+       }
+       /* bzlib.h is not const-correct, so we need this one bit
+        * of ugly hackery to convert a const * pointer to
+        * a non-const pointer. */
+       strm->next_in = (char *)(uintptr_t)(const void *)lastrm->next_in;
+       strm->avail_in = lastrm->avail_in;
+       strm->total_in_lo32 = (uint32_t)(lastrm->total_in & 0xffffffff);
+       strm->total_in_hi32 = (uint32_t)(lastrm->total_in >> 32);
+       strm->next_out = (char *)lastrm->next_out;
+       strm->avail_out = lastrm->avail_out;
+       strm->total_out_lo32 = (uint32_t)(lastrm->total_out & 0xffffffff);
+       strm->total_out_hi32 = (uint32_t)(lastrm->total_out >> 32);
+       if (BZ2_bzCompressInit(strm, level, 0, 30) != BZ_OK) {
+               free(strm);
+               lastrm->real_stream = NULL;
+               archive_set_error(a, ARCHIVE_ERRNO_MISC,
+                   "Internal error initializing compression library");
+               return (ARCHIVE_FATAL);
+       }
+       lastrm->real_stream = strm;
+       lastrm->valid = 1;
+       lastrm->code = compression_code_bzip2;
+       lastrm->end = compression_end_bzip2;
+       return (ARCHIVE_OK);
+}
+
+static int
+compression_code_bzip2(struct archive *a,
+    struct la_zstream *lastrm, enum la_zaction action)
+{
+       bz_stream *strm;
+       int r;
+
+       strm = (bz_stream *)lastrm->real_stream;
+       /* bzlib.h is not const-correct, so we need this one bit
+        * of ugly hackery to convert a const * pointer to
+        * a non-const pointer. */
+       strm->next_in = (char *)(uintptr_t)(const void *)lastrm->next_in;
+       strm->avail_in = lastrm->avail_in;
+       strm->total_in_lo32 = (uint32_t)(lastrm->total_in & 0xffffffff);
+       strm->total_in_hi32 = (uint32_t)(lastrm->total_in >> 32);
+       strm->next_out = (char *)lastrm->next_out;
+       strm->avail_out = lastrm->avail_out;
+       strm->total_out_lo32 = (uint32_t)(lastrm->total_out & 0xffffffff);
+       strm->total_out_hi32 = (uint32_t)(lastrm->total_out >> 32);
+       r = BZ2_bzCompress(strm,
+           (action == ARCHIVE_Z_FINISH)? BZ_FINISH: BZ_RUN);
+       lastrm->next_in = (const unsigned char *)strm->next_in;
+       lastrm->avail_in = strm->avail_in;
+       lastrm->total_in =
+           (((uint64_t)(uint32_t)strm->total_in_hi32) << 32)
+           + (uint64_t)(uint32_t)strm->total_in_lo32;
+       lastrm->next_out = (unsigned char *)strm->next_out;
+       lastrm->avail_out = strm->avail_out;
+       lastrm->total_out =
+           (((uint64_t)(uint32_t)strm->total_out_hi32) << 32)
+           + (uint64_t)(uint32_t)strm->total_out_lo32;
+       switch (r) {
+       case BZ_RUN_OK:     /* Non-finishing */
+       case BZ_FINISH_OK:  /* Finishing: There's more work to do */
+               return (ARCHIVE_OK);
+       case BZ_STREAM_END: /* Finishing: all done */
+               /* Only occurs in finishing case */
+               return (ARCHIVE_EOF);
+       default:
+               /* Any other return value indicates an error */
+               archive_set_error(a, ARCHIVE_ERRNO_MISC,
+                   "Bzip2 compression failed:"
+                   " BZ2_bzCompress() call returned status %d", r);
+               return (ARCHIVE_FATAL);
+       }
+}
+
+static int
+compression_end_bzip2(struct archive *a, struct la_zstream *lastrm)
+{
+       bz_stream *strm;
+       int r;
+
+       strm = (bz_stream *)lastrm->real_stream;
+       r = BZ2_bzCompressEnd(strm);
+       free(strm);
+       lastrm->real_stream = NULL;
+       lastrm->valid = 0;
+       if (r != BZ_OK) {
+               archive_set_error(a, ARCHIVE_ERRNO_MISC,
+                   "Failed to clean up compressor");
+               return (ARCHIVE_FATAL);
+       }
+       return (ARCHIVE_OK);
+}
+
+#else
+static int
+compression_init_encoder_bzip2(struct archive *a,
+    struct la_zstream *lastrm, int level)
+{
+
+       (void) level; /* UNUSED */
+       if (lastrm->valid)
+               compression_end(a, lastrm);
+       return (compression_unsupported_encoder(a, lastrm, "bzip2"));
+}
+#endif
+
+#if defined(HAVE_LZMA_H)
+static int
+compression_init_encoder_lzma(struct archive *a,
+    struct la_zstream *lastrm, int level)
+{
+       static const lzma_stream lzma_init_data = LZMA_STREAM_INIT;
+       lzma_stream *strm;
+       lzma_options_lzma lzma_opt;
+       int r;
+
+       if (lastrm->valid)
+               compression_end(a, lastrm);
+       if (lzma_lzma_preset(&lzma_opt, level)) {
+               lastrm->real_stream = NULL;
+               archive_set_error(a, ENOMEM,
+                   "Internal error initializing compression library");
+               return (ARCHIVE_FATAL);
+       }
+       strm = calloc(1, sizeof(*strm));
+       if (strm == NULL) {
+               archive_set_error(a, ENOMEM,
+                   "Can't allocate memory for lzma stream");
+               return (ARCHIVE_FATAL);
+       }
+       *strm = lzma_init_data;
+       r = lzma_alone_encoder(strm, &lzma_opt);
+       switch (r) {
+       case LZMA_OK:
+               lastrm->real_stream = strm;
+               lastrm->valid = 1;
+               lastrm->code = compression_code_lzma;
+               lastrm->end = compression_end_lzma;
+               r = ARCHIVE_OK;
+               break;
+       case LZMA_MEM_ERROR:
+               free(strm);
+               lastrm->real_stream = NULL;
+               archive_set_error(a, ENOMEM,
+                   "Internal error initializing compression library: "
+                   "Cannot allocate memory");
+               r =  ARCHIVE_FATAL;
+               break;
+        default:
+               free(strm);
+               lastrm->real_stream = NULL;
+               archive_set_error(a, ARCHIVE_ERRNO_MISC,
+                   "Internal error initializing compression library: "
+                   "It's a bug in liblzma");
+               r =  ARCHIVE_FATAL;
+               break;
+       }
+       return (r);
+}
+
+static int
+compression_init_encoder_xz(struct archive *a,
+    struct la_zstream *lastrm, int level)
+{
+       static const lzma_stream lzma_init_data = LZMA_STREAM_INIT;
+       lzma_stream *strm;
+       lzma_filter *lzmafilters;
+       lzma_options_lzma lzma_opt;
+       int r;
+
+       if (lastrm->valid)
+               compression_end(a, lastrm);
+       strm = calloc(1, sizeof(*strm) + sizeof(*lzmafilters) * 2);
+       if (strm == NULL) {
+               archive_set_error(a, ENOMEM,
+                   "Can't allocate memory for xz stream");
+               return (ARCHIVE_FATAL);
+       }
+       lzmafilters = (lzma_filter *)(strm+1);
+       if (level > 6)
+               level = 6;
+       if (lzma_lzma_preset(&lzma_opt, level)) {
+               lastrm->real_stream = NULL;
+               archive_set_error(a, ENOMEM,
+                   "Internal error initializing compression library");
+               return (ARCHIVE_FATAL);
+       }
+       lzmafilters[0].id = LZMA_FILTER_LZMA2;
+       lzmafilters[0].options = &lzma_opt;
+       lzmafilters[1].id = LZMA_VLI_UNKNOWN;/* Terminate */
+
+       *strm = lzma_init_data;
+       r = lzma_stream_encoder(strm, lzmafilters, LZMA_CHECK_CRC64);
+       switch (r) {
+       case LZMA_OK:
+               lastrm->real_stream = strm;
+               lastrm->valid = 1;
+               lastrm->code = compression_code_lzma;
+               lastrm->end = compression_end_lzma;
+               r = ARCHIVE_OK;
+               break;
+       case LZMA_MEM_ERROR:
+               free(strm);
+               lastrm->real_stream = NULL;
+               archive_set_error(a, ENOMEM,
+                   "Internal error initializing compression library: "
+                   "Cannot allocate memory");
+               r =  ARCHIVE_FATAL;
+               break;
+        default:
+               free(strm);
+               lastrm->real_stream = NULL;
+               archive_set_error(a, ARCHIVE_ERRNO_MISC,
+                   "Internal error initializing compression library: "
+                   "It's a bug in liblzma");
+               r =  ARCHIVE_FATAL;
+               break;
+       }
+       return (r);
+}
+
+static int
+compression_code_lzma(struct archive *a,
+    struct la_zstream *lastrm, enum la_zaction action)
+{
+       lzma_stream *strm;
+       int r;
+
+       strm = (lzma_stream *)lastrm->real_stream;
+       strm->next_in = lastrm->next_in;
+       strm->avail_in = lastrm->avail_in;
+       strm->total_in = lastrm->total_in;
+       strm->next_out = lastrm->next_out;
+       strm->avail_out = lastrm->avail_out;
+       strm->total_out = lastrm->total_out;
+       r = lzma_code(strm,
+           (action == ARCHIVE_Z_FINISH)? LZMA_FINISH: LZMA_RUN);
+       lastrm->next_in = strm->next_in;
+       lastrm->avail_in = strm->avail_in;
+       lastrm->total_in = strm->total_in;
+       lastrm->next_out = strm->next_out;
+       lastrm->avail_out = strm->avail_out;
+       lastrm->total_out = strm->total_out;
+       switch (r) {
+       case LZMA_OK:
+               /* Non-finishing case */
+               return (ARCHIVE_OK);
+       case LZMA_STREAM_END:
+               /* This return can only occur in finishing case. */
+               return (ARCHIVE_EOF);
+       case LZMA_MEMLIMIT_ERROR:
+               archive_set_error(a, ENOMEM,
+                   "lzma compression error:"
+                   " %ju MiB would have been needed",
+                   (lzma_memusage(strm) + 1024 * 1024 -1)
+                   / (1024 * 1024));
+               return (ARCHIVE_FATAL);
+       default:
+               /* Any other return value indicates an error */
+               archive_set_error(a, ARCHIVE_ERRNO_MISC,
+                   "lzma compression failed:"
+                   " lzma_code() call returned status %d", r);
+               return (ARCHIVE_FATAL);
+       }
+}
+
+static int
+compression_end_lzma(struct archive *a, struct la_zstream *lastrm)
+{
+       lzma_stream *strm;
+
+       (void)a; /* UNUSED */
+       strm = (lzma_stream *)lastrm->real_stream;
+       lzma_end(strm);
+       free(strm);
+       lastrm->valid = 0;
+       lastrm->real_stream = NULL;
+       return (ARCHIVE_OK);
+}
+#else
+static int
+compression_init_encoder_lzma(struct archive *a,
+    struct la_zstream *lastrm, int level)
+{
+
+       (void) level; /* UNUSED */
+       if (lastrm->valid)
+               compression_end(a, lastrm);
+       return (compression_unsupported_encoder(a, lastrm, "lzma"));
+}
+static int
+compression_init_encoder_xz(struct archive *a,
+    struct la_zstream *lastrm, int level)
+{
+
+       (void) level; /* UNUSED */
+       if (lastrm->valid)
+               compression_end(a, lastrm);
+       return (compression_unsupported_encoder(a, lastrm, "xz"));
+}
+#endif
+
+static int
+xar_compression_init_encoder(struct archive_write *a)
+{
+       struct xar *xar;
+       int r;
+
+       xar = (struct xar *)a->format_data;
+       switch (xar->opt_compression) {
+       case GZIP:
+               r = compression_init_encoder_gzip(
+                   &(a->archive), &(xar->stream),
+                   xar->opt_compression_level, 1);
+               break;
+       case BZIP2:
+               r = compression_init_encoder_bzip2(
+                   &(a->archive), &(xar->stream),
+                   xar->opt_compression_level);
+               break;
+       case LZMA:
+               r = compression_init_encoder_lzma(
+                   &(a->archive), &(xar->stream),
+                   xar->opt_compression_level);
+               break;
+       case XZ:
+               r = compression_init_encoder_xz(
+                   &(a->archive), &(xar->stream),
+                   xar->opt_compression_level);
+               break;
+       default:
+               r = ARCHIVE_OK;
+               break;
+       }
+       if (r == ARCHIVE_OK) {
+               xar->stream.total_in = 0;
+               xar->stream.next_out = xar->wbuff;
+               xar->stream.avail_out = sizeof(xar->wbuff);
+               xar->stream.total_out = 0;
+       }
+
+       return (r);
+}
+
+static int
+compression_code(struct archive *a, struct la_zstream *lastrm,
+    enum la_zaction action)
+{
+       if (lastrm->valid)
+               return (lastrm->code(a, lastrm, action));
+       return (ARCHIVE_OK);
+}
+
+static int
+compression_end(struct archive *a, struct la_zstream *lastrm)
+{
+       if (lastrm->valid)
+               return (lastrm->end(a, lastrm));
+       return (ARCHIVE_OK);
+}
+
+
+static int
+save_xattrs(struct archive_write *a, struct file *file)
+{
+       struct xar *xar;
+       const char *name;
+       const void *value;
+       struct heap_data *heap;
+       size_t size;
+       int count, r;
+
+       xar = (struct xar *)a->format_data;
+       count = archive_entry_xattr_reset(file->entry);
+       if (count == 0)
+               return (ARCHIVE_OK);
+       while (count--) {
+               archive_entry_xattr_next(file->entry,
+                   &name, &value, &size);
+               checksum_init(&(xar->a_sumwrk), xar->opt_sumalg);
+               checksum_init(&(xar->e_sumwrk), xar->opt_sumalg);
+
+               heap = calloc(1, sizeof(*heap));
+               if (heap == NULL) {
+                       archive_set_error(&a->archive, ENOMEM,
+                           "Can't allocate memory for xattr");
+                       return (ARCHIVE_FATAL);
+               }
+               heap->id = file->ea_idx++;
+               heap->temp_offset = xar->temp_offset;
+               heap->size = size;/* save a extracted size */
+               heap->compression = xar->opt_compression;
+               /* Get a extracted sumcheck value. */
+               checksum_update(&(xar->e_sumwrk), value, size);
+               checksum_final(&(xar->e_sumwrk), &(heap->e_sum));
+
+               /*
+                * Not compression to xattr is simple way.
+                */
+               if (heap->compression == NONE) {
+                       checksum_update(&(xar->a_sumwrk), value, size);
+                       checksum_final(&(xar->a_sumwrk), &(heap->a_sum));
+                       if (write_to_temp(a, value, size)
+                           != ARCHIVE_OK)
+                               return (ARCHIVE_FATAL);
+                       heap->length = size;
+                       /* Add heap to the tail of file->xattr. */
+                       heap->next = NULL;
+                       *file->xattr.last = heap;
+                       file->xattr.last = &(heap->next);
+                       /* Next xattr */
+                       continue;
+               }
+
+               /*
+                * Init compression library.
+                */
+               r = xar_compression_init_encoder(a);
+               if (r != ARCHIVE_OK) {
+                       free(heap);
+                       return (ARCHIVE_FATAL);
+               }
+
+               xar->stream.next_in = (const unsigned char *)value;
+               xar->stream.avail_in = size;
+               for (;;) {
+                       r = compression_code(&(a->archive),
+                           &(xar->stream), ARCHIVE_Z_FINISH);
+                       if (r != ARCHIVE_OK && r != ARCHIVE_EOF) {
+                               free(heap);
+                               return (ARCHIVE_FATAL);
+                       }
+                       size = sizeof(xar->wbuff) - xar->stream.avail_out;
+                       checksum_update(&(xar->a_sumwrk),
+                           xar->wbuff, size);
+                       if (write_to_temp(a, xar->wbuff, size)
+                           != ARCHIVE_OK)
+                               return (ARCHIVE_FATAL);
+                       if (r == ARCHIVE_OK) {
+                               xar->stream.next_out = xar->wbuff;
+                               xar->stream.avail_out = sizeof(xar->wbuff);
+                       } else {
+                               checksum_final(&(xar->a_sumwrk),
+                                   &(heap->a_sum));
+                               heap->length = xar->stream.total_out;
+                               /* Add heap to the tail of file->xattr. */
+                               heap->next = NULL;
+                               *file->xattr.last = heap;
+                               file->xattr.last = &(heap->next);
+                               break;
+                       }
+               }
+               /* Clean up compression library. */
+               r = compression_end(&(a->archive), &(xar->stream));
+               if (r != ARCHIVE_OK)
+                       return (ARCHIVE_FATAL);
+       }
+       return (ARCHIVE_OK);
+}
+
+static size_t
+utf8_encode(struct archive_string *utf8, const wchar_t *wval)
+{
+       int utf8len;
+       const wchar_t *wp;
+       unsigned long wc;
+       char *utf8_value, *p;
+
+       archive_string_empty(utf8);
+       if (wval == NULL)
+               return (0);
+       utf8len = 0;
+       for (wp = wval; *wp != L'\0'; ) {
+               wc = *wp++;
+
+               if (wc >= 0xd800 && wc <= 0xdbff
+                   && *wp >= 0xdc00 && *wp <= 0xdfff) {
+                       /* This is a surrogate pair.  Combine into a
+                        * full Unicode value before encoding into
+                        * UTF-8. */
+                       wc = (wc - 0xd800) << 10; /* High 10 bits */
+                       wc += (*wp++ - 0xdc00); /* Low 10 bits */
+                       wc += 0x10000; /* Skip BMP */
+               }
+               if (wc <= 0x7f)
+                       utf8len++;
+               else if (wc <= 0x7ff)
+                       utf8len += 2;
+               else if (wc <= 0xffff)
+                       utf8len += 3;
+               else if (wc <= 0x1fffff)
+                       utf8len += 4;
+               else if (wc <= 0x3ffffff)
+                       utf8len += 5;
+               else if (wc <= 0x7fffffff)
+                       utf8len += 6;
+               /* Ignore larger values; UTF-8 can't encode them. */
+       }
+
+       archive_string_ensure(utf8, utf8len + 1);
+       utf8->length = utf8len;
+       utf8_value = utf8->s;
+
+       for (wp = wval, p = utf8_value; *wp != L'\0'; ) {
+               wc = *wp++;
+               if (wc >= 0xd800 && wc <= 0xdbff
+                   && *wp >= 0xdc00 && *wp <= 0xdfff) {
+                       /* Combine surrogate pair. */
+                       wc = (wc - 0xd800) << 10;
+                       wc += *wp++ - 0xdc00 + 0x10000;
+               }
+               if (wc <= 0x7f) {
+                       *p++ = (char)wc;
+               } else if (wc <= 0x7ff) {
+                       p[0] = 0xc0 | ((wc >> 6) & 0x1f);
+                       p[1] = 0x80 | (wc & 0x3f);
+                       p += 2;
+               } else if (wc <= 0xffff) {
+                       p[0] = 0xe0 | ((wc >> 12) & 0x0f);
+                       p[1] = 0x80 | ((wc >> 6) & 0x3f);
+                       p[2] = 0x80 | (wc & 0x3f);
+                       p += 3;
+               } else if (wc <= 0x1fffff) {
+                       p[0] = 0xf0 | ((wc >> 18) & 0x07);
+                       p[1] = 0x80 | ((wc >> 12) & 0x3f);
+                       p[2] = 0x80 | ((wc >> 6) & 0x3f);
+                       p[3] = 0x80 | (wc & 0x3f);
+                       p += 4;
+               } else if (wc <= 0x3ffffff) {
+                       p[0] = 0xf8 | ((wc >> 24) & 0x03);
+                       p[1] = 0x80 | ((wc >> 18) & 0x3f);
+                       p[2] = 0x80 | ((wc >> 12) & 0x3f);
+                       p[3] = 0x80 | ((wc >> 6) & 0x3f);
+                       p[4] = 0x80 | (wc & 0x3f);
+                       p += 5;
+               } else if (wc <= 0x7fffffff) {
+                       p[0] = 0xfc | ((wc >> 30) & 0x01);
+                       p[1] = 0x80 | ((wc >> 24) & 0x3f);
+                       p[1] = 0x80 | ((wc >> 18) & 0x3f);
+                       p[2] = 0x80 | ((wc >> 12) & 0x3f);
+                       p[3] = 0x80 | ((wc >> 6) & 0x3f);
+                       p[4] = 0x80 | (wc & 0x3f);
+                       p += 6;
+               }
+               /* Ignore larger values; UTF-8 can't encode them. */
+       }
+       *p = '\0';
+
+       return (utf8->length);
+}
+
+static int
+getalgsize(enum sumalg sumalg)
+{
+       switch (sumalg) {
+       default:
+       case CKSUM_NONE:
+               return (0);
+       case CKSUM_SHA1:
+               return (SHA1_SIZE);
+       case CKSUM_MD5:
+               return (MD5_SIZE);
+       }
+}
+
+static const char *
+getalgname(enum sumalg sumalg)
+{
+       switch (sumalg) {
+       default:
+       case CKSUM_NONE:
+               return (NULL);
+       case CKSUM_SHA1:
+               return (SHA1_NAME);
+       case CKSUM_MD5:
+               return (MD5_NAME);
+       }
+}
+
+#endif /* Support xar format */
+
index 8fb4bcca928cdb5d1e36329ee389ba60742e620a..81207501557d941c754fc812038b114828b4e2c2 100644 (file)
@@ -122,6 +122,8 @@ IF(ENABLE_TEST)
     test_write_format_tar.c
     test_write_format_tar_empty.c
     test_write_format_tar_ustar.c
+    test_write_format_xar.c
+    test_write_format_xar_empty.c
     test_write_format_zip.c
     test_write_format_zip_empty.c
     test_write_format_zip_no_compression.c
diff --git a/libarchive/test/test_write_format_xar.c b/libarchive/test/test_write_format_xar.c
new file mode 100644 (file)
index 0000000..b1f3b34
--- /dev/null
@@ -0,0 +1,309 @@
+/*-
+ * Copyright (c) 2003-2008 Tim Kientzle
+ * Copyright (c) 2010 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.
+ * 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 void
+test_xar(const char *option)
+{
+       char buff2[64];
+       size_t buffsize = 1000000;
+       char *buff;
+       struct archive_entry *ae;
+       struct archive *a;
+       size_t used;
+       int i;
+       const char *name;
+       const void *value;
+       size_t size;
+
+       /* Create a new archive in memory. */
+       assert((a = archive_write_new()) != NULL);
+       assertA(0 == archive_write_set_format_xar(a));
+       assertA(0 == archive_write_set_compression_none(a));
+       if (option != NULL &&
+           archive_write_set_options(a, option) != ARCHIVE_OK) {
+               skipping("option `%s` is not supported on this platform", option);
+               assertEqualIntA(a, ARCHIVE_OK, archive_write_free(a));
+               return;
+       }
+
+       buff = malloc(buffsize); /* million bytes of work area */
+       assert(buff != NULL);
+
+       assertA(0 == archive_write_open_memory(a, buff, buffsize, &used));
+
+       /*
+        * "file" has a bunch of attributes and 8 bytes of data and
+        * 7 bytes of xattr data and 3 bytes of xattr.
+        */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_set_atime(ae, 2, 20);
+       archive_entry_set_ctime(ae, 4, 40);
+       archive_entry_set_mtime(ae, 5, 50);
+       archive_entry_copy_pathname(ae, "file");
+       archive_entry_set_mode(ae, AE_IFREG | 0755);
+       archive_entry_set_nlink(ae, 2);
+       archive_entry_set_size(ae, 8);
+       archive_entry_xattr_add_entry(ae, "user.data1", "ABCDEFG", 7);
+       archive_entry_xattr_add_entry(ae, "user.data2", "XYZ", 3);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+       archive_entry_free(ae);
+       assertEqualIntA(a, 8, archive_write_data(a, "12345678", 9));
+
+       /*
+        * "file2" is symbolic link to file
+        */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_set_atime(ae, 2, 20);
+       archive_entry_set_ctime(ae, 4, 40);
+       archive_entry_set_mtime(ae, 5, 50);
+       archive_entry_copy_pathname(ae, "file2");
+       archive_entry_copy_symlink(ae, "file");
+       archive_entry_set_mode(ae, AE_IFLNK | 0755);
+       archive_entry_set_size(ae, 0);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+       archive_entry_free(ae);
+
+       /*
+        * "dir/file3" has a bunch of attributes and 8 bytes of data.
+        */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_set_atime(ae, 2, 20);
+       archive_entry_set_ctime(ae, 4, 40);
+       archive_entry_set_mtime(ae, 5, 50);
+       archive_entry_copy_pathname(ae, "dir/file");
+       archive_entry_set_mode(ae, AE_IFREG | 0755);
+       archive_entry_set_size(ae, 8);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+       archive_entry_free(ae);
+       assertEqualIntA(a, 8, archive_write_data(a, "abcdefgh", 9));
+
+       /*
+        * "dir/dir2/file4" is hard link to file
+        */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_set_atime(ae, 2, 20);
+       archive_entry_set_ctime(ae, 4, 40);
+       archive_entry_set_mtime(ae, 5, 50);
+       archive_entry_copy_pathname(ae, "dir/dir2/file4");
+       archive_entry_copy_hardlink(ae, "file");
+       archive_entry_set_mode(ae, AE_IFREG | 0755);
+       archive_entry_set_nlink(ae, 2);
+       archive_entry_set_size(ae, 0);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+       archive_entry_free(ae);
+
+       /*
+        * "dir/dir3" is a directory
+        */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_set_atime(ae, 2, 20);
+       archive_entry_set_ctime(ae, 4, 40);
+       archive_entry_set_mtime(ae, 5, 50);
+       archive_entry_copy_pathname(ae, "dir/dir3");
+       archive_entry_set_mode(ae, AE_IFDIR | 0755);
+       archive_entry_unset_size(ae);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+       archive_entry_free(ae);
+
+       /*
+        * Add a wrong path "dir/dir2/file4/wrong"
+        */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_set_atime(ae, 2, 20);
+       archive_entry_set_ctime(ae, 4, 40);
+       archive_entry_set_mtime(ae, 5, 50);
+       archive_entry_copy_pathname(ae, "dir/dir2/file4/wrong");
+       archive_entry_set_mode(ae, AE_IFREG | 0755);
+       archive_entry_set_nlink(ae, 1);
+       archive_entry_set_size(ae, 0);
+       assertEqualIntA(a, ARCHIVE_FAILED, archive_write_header(a, ae));
+       archive_entry_free(ae);
+
+       /*
+        * XXX TODO XXX Archive directory, other file types.
+        * Archive extended attributes, ACLs, other metadata.
+        * Verify they get read back correctly.
+        */
+
+       /* Close out the archive. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_free(a));
+
+       /*
+        *
+        * Now, read the data back.
+        *
+        */
+       assert((a = archive_read_new()) != NULL);
+       assertEqualIntA(a, 0, archive_read_support_format_all(a));
+       assertEqualIntA(a, 0, archive_read_support_compression_all(a));
+       assertEqualIntA(a, 0, archive_read_open_memory(a, buff, used));
+
+       /*
+        * Read "file"
+        */
+       assertEqualIntA(a, 0, archive_read_next_header(a, &ae));
+       assertEqualInt(2, archive_entry_atime(ae));
+       assertEqualInt(0, archive_entry_atime_nsec(ae));
+       assertEqualInt(4, archive_entry_ctime(ae));
+       assertEqualInt(0, archive_entry_ctime_nsec(ae));
+       assertEqualInt(5, archive_entry_mtime(ae));
+       assertEqualInt(0, archive_entry_mtime_nsec(ae));
+       assertEqualString("file", archive_entry_pathname(ae));
+       assert((AE_IFREG | 0755) == archive_entry_mode(ae));
+       assertEqualInt(2, archive_entry_nlink(ae));
+       assertEqualInt(8, archive_entry_size(ae));
+       assertEqualInt(2, i = archive_entry_xattr_reset(ae));
+       assertEqualInt(ARCHIVE_OK,
+           archive_entry_xattr_next(ae, &name, &value, &size));
+       assertEqualString("user.data2", name);
+       assertEqualMem(value, "XYZ", 3);
+       assertEqualInt(ARCHIVE_OK,
+           archive_entry_xattr_next(ae, &name, &value, &size));
+       assertEqualString("user.data1", name);
+       assertEqualMem(value, "ABCDEFG", 7);
+       assertEqualIntA(a, 8, archive_read_data(a, buff2, 10));
+       assertEqualMem(buff2, "12345678", 8);
+
+       /*
+        * Read "file2"
+        */
+       assertEqualIntA(a, 0, archive_read_next_header(a, &ae));
+       assert(archive_entry_atime_is_set(ae));
+       assertEqualInt(2, archive_entry_atime(ae));
+       assertEqualInt(0, archive_entry_atime_nsec(ae));
+       assert(archive_entry_ctime_is_set(ae));
+       assertEqualInt(4, archive_entry_ctime(ae));
+       assertEqualInt(0, archive_entry_ctime_nsec(ae));
+       assert(archive_entry_mtime_is_set(ae));
+       assertEqualInt(5, archive_entry_mtime(ae));
+       assertEqualInt(0, archive_entry_mtime_nsec(ae));
+       assertEqualString("file2", archive_entry_pathname(ae));
+       assertEqualString("file", archive_entry_symlink(ae));
+       assert((AE_IFLNK | 0755) == archive_entry_mode(ae));
+       assertEqualInt(0, archive_entry_size(ae));
+
+       /*
+        * Read "dir/file3"
+        */
+       assertEqualIntA(a, 0, archive_read_next_header(a, &ae));
+       assertEqualInt(2, archive_entry_atime(ae));
+       assertEqualInt(0, archive_entry_atime_nsec(ae));
+       assertEqualInt(4, archive_entry_ctime(ae));
+       assertEqualInt(0, archive_entry_ctime_nsec(ae));
+       assertEqualInt(5, archive_entry_mtime(ae));
+       assertEqualInt(0, archive_entry_mtime_nsec(ae));
+       assertEqualString("dir/file", archive_entry_pathname(ae));
+       assert((AE_IFREG | 0755) == archive_entry_mode(ae));
+       assertEqualInt(8, archive_entry_size(ae));
+       assertEqualIntA(a, 8, archive_read_data(a, buff2, 10));
+       assertEqualMem(buff2, "abcdefgh", 8);
+
+       /*
+        * Read "dir/dir2/file4"
+        */
+       assertEqualIntA(a, 0, archive_read_next_header(a, &ae));
+       assert(archive_entry_atime_is_set(ae));
+       assertEqualInt(2, archive_entry_atime(ae));
+       assertEqualInt(0, archive_entry_atime_nsec(ae));
+       assert(archive_entry_ctime_is_set(ae));
+       assertEqualInt(4, archive_entry_ctime(ae));
+       assertEqualInt(0, archive_entry_ctime_nsec(ae));
+       assert(archive_entry_mtime_is_set(ae));
+       assertEqualInt(5, archive_entry_mtime(ae));
+       assertEqualInt(0, archive_entry_mtime_nsec(ae));
+       assertEqualString("dir/dir2/file4", archive_entry_pathname(ae));
+       assertEqualString("file", archive_entry_hardlink(ae));
+       assert((AE_IFREG | 0755) == archive_entry_mode(ae));
+       assertEqualInt(2, archive_entry_nlink(ae));
+       assertEqualInt(0, archive_entry_size(ae));
+
+       /*
+        * Read "dir/dir3"
+        */
+       assertEqualIntA(a, 0, archive_read_next_header(a, &ae));
+       assert(archive_entry_atime_is_set(ae));
+       assertEqualInt(2, archive_entry_atime(ae));
+       assertEqualInt(0, archive_entry_atime_nsec(ae));
+       assert(archive_entry_ctime_is_set(ae));
+       assertEqualInt(4, archive_entry_ctime(ae));
+       assertEqualInt(0, archive_entry_ctime_nsec(ae));
+       assert(archive_entry_mtime_is_set(ae));
+       assertEqualInt(5, archive_entry_mtime(ae));
+       assertEqualInt(0, archive_entry_mtime_nsec(ae));
+       assertEqualString("dir/dir3", archive_entry_pathname(ae));
+       assert((AE_IFDIR | 0755) == archive_entry_mode(ae));
+
+       /*
+        * Verify the end of the archive.
+        */
+       assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_free(a));
+
+       free(buff);
+}
+
+DEFINE_TEST(test_write_format_xar)
+{
+       /* Default mode. */
+       test_xar(NULL);
+
+       /* Disable TOC checksum. */
+       test_xar("!toc-checksum");
+       /* Specify TOC checksum type to sha1. */
+       test_xar("toc-checksum=sha1");
+       /* Specify TOC checksum type to md5. */
+       test_xar("toc-checksum=md5");
+
+       /* Disable file checksum. */
+       test_xar("!checksum");
+       /* Specify file checksum type to sha1. */
+       test_xar("checksum=sha1");
+       /* Specify file checksum type to md5. */
+       test_xar("checksum=md5");
+
+       /* Disable compression. */
+       test_xar("!compression");
+       /* Specify compression type to gzip. */
+       test_xar("compression=gzip");
+       test_xar("compression=gzip,compression-level=1");
+       test_xar("compression=gzip,compression-level=9");
+       /* Specify compression type to bzip2. */
+       test_xar("compression=bzip2");
+       test_xar("compression=bzip2,compression-level=1");
+       test_xar("compression=bzip2,compression-level=9");
+       /* Specify compression type to lzma. */
+       test_xar("compression=lzma");
+       test_xar("compression=lzma,compression-level=1");
+       test_xar("compression=lzma,compression-level=9");
+       /* Specify compression type to xz. */
+       test_xar("compression=xz");
+       test_xar("compression=xz,compression-level=1");
+       test_xar("compression=xz,compression-level=9");
+}
diff --git a/libarchive/test/test_write_format_xar_empty.c b/libarchive/test/test_write_format_xar_empty.c
new file mode 100644 (file)
index 0000000..ae37690
--- /dev/null
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 2010 Michihiro NAKAJIMA
+ * Copyright (c) 2008 Anselm Strauss
+ * 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.
+ * 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.
+ */
+
+/*
+ * Development supported by Google Summer of Code 2008.
+ */
+
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+DEFINE_TEST(test_write_format_xar_empty)
+{
+       struct archive *a;
+       char buff[256];
+       size_t used;
+
+       /* Xar format: Create a new archive in memory. */
+       assert((a = archive_write_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_xar(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_set_compression_none(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_set_bytes_per_block(a, 1));
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_set_bytes_in_last_block(a, 1));
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_write_open_memory(a, buff, sizeof(buff), &used));
+
+       /* Close out the archive without writing anything. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_write_free(a));
+
+       /* Verify the correct format for an empy Xar archive. */
+       assertEqualInt(used, 0);
+}