]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add support for writing v7 tar format.
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Thu, 18 Oct 2012 03:40:19 +0000 (12:40 +0900)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Sun, 21 Oct 2012 03:35:15 +0000 (12:35 +0900)
Makefile.am
libarchive/CMakeLists.txt
libarchive/archive.h
libarchive/archive_write_set_format_by_name.c
libarchive/archive_write_set_format_v7tar.c [new file with mode: 0644]
libarchive/test/CMakeLists.txt
libarchive/test/test_write_format_tar_v7tar.c [new file with mode: 0644]

index dab07f686c4a2fa7e503ab191ecf49f1456b0f4b..556c315bf6c746cb4a1a985ff689e260c1d8d8a2 100644 (file)
@@ -198,6 +198,7 @@ libarchive_la_SOURCES=                                              \
        libarchive/archive_write_set_format_pax.c               \
        libarchive/archive_write_set_format_shar.c              \
        libarchive/archive_write_set_format_ustar.c             \
+       libarchive/archive_write_set_format_v7tar.c             \
        libarchive/archive_write_set_format_gnutar.c            \
        libarchive/archive_write_set_format_xar.c               \
        libarchive/archive_write_set_format_zip.c               \
@@ -456,6 +457,7 @@ libarchive_test_SOURCES=                                    \
        libarchive/test/test_write_format_tar_empty.c           \
        libarchive/test/test_write_format_tar_sparse.c          \
        libarchive/test/test_write_format_tar_ustar.c           \
+       libarchive/test/test_write_format_tar_v7tar.c           \
        libarchive/test/test_write_format_xar.c                 \
        libarchive/test/test_write_format_xar_empty.c           \
        libarchive/test/test_write_format_zip.c                 \
index 2d9809b23998ebd4e0cebffc53154eca6c270b45..8927612cd6c83b464249d507ce1e82c193ceb011 100644 (file)
@@ -124,6 +124,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_v7tar.c
   archive_write_set_format_xar.c
   archive_write_set_format_zip.c
   archive_write_set_options.c
index d73e62519d89bae75f96490c9b3cffa47af35d08..6ee542cedb0699a7f6238de970edf044de664ffe 100644 (file)
@@ -654,6 +654,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_v7tar(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_zip_set_compression_deflate(struct archive *);
index 1210f35a51ba0e85798f455c09c6393a67fbf4e5..af3105e482d646af290fc51122b115a33b3a33a1 100644 (file)
@@ -59,6 +59,7 @@ struct { const char *name; int (*setter)(struct archive *); } names[] =
        { "mtree-classic",      archive_write_set_format_mtree_classic },
        { "newc",       archive_write_set_format_cpio_newc },
        { "odc",        archive_write_set_format_cpio },
+       { "oldtar",     archive_write_set_format_v7tar },
        { "pax",        archive_write_set_format_pax },
        { "paxr",       archive_write_set_format_pax_restricted },
        { "posix",      archive_write_set_format_pax },
@@ -66,6 +67,8 @@ 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 },
+       { "v7tar",      archive_write_set_format_v7tar },
+       { "v7",         archive_write_set_format_v7tar },
        { "xar",        archive_write_set_format_xar },
        { "zip",        archive_write_set_format_zip },
        { NULL,         NULL }
diff --git a/libarchive/archive_write_set_format_v7tar.c b/libarchive/archive_write_set_format_v7tar.c
new file mode 100644 (file)
index 0000000..17efbaf
--- /dev/null
@@ -0,0 +1,661 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * Copyright (c) 2011-2012 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 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_ERRNO_H
+#include <errno.h>
+#endif
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "archive_entry_locale.h"
+#include "archive_private.h"
+#include "archive_write_private.h"
+
+struct v7tar {
+       uint64_t        entry_bytes_remaining;
+       uint64_t        entry_padding;
+
+       struct archive_string_conv *opt_sconv;
+       struct archive_string_conv *sconv_default;
+       int     init_default_conversion;
+};
+
+/*
+ * Define structure of POSIX 'v7tar' tar header.
+ */
+#define        V7TAR_name_offset 0
+#define        V7TAR_name_size 100
+#define        V7TAR_mode_offset 100
+#define        V7TAR_mode_size 6
+#define        V7TAR_mode_max_size 8
+#define        V7TAR_uid_offset 108
+#define        V7TAR_uid_size 6
+#define        V7TAR_uid_max_size 8
+#define        V7TAR_gid_offset 116
+#define        V7TAR_gid_size 6
+#define        V7TAR_gid_max_size 8
+#define        V7TAR_size_offset 124
+#define        V7TAR_size_size 11
+#define        V7TAR_size_max_size 12
+#define        V7TAR_mtime_offset 136
+#define        V7TAR_mtime_size 11
+#define        V7TAR_mtime_max_size 12
+#define        V7TAR_checksum_offset 148
+#define        V7TAR_checksum_size 8
+#define        V7TAR_typeflag_offset 156
+#define        V7TAR_typeflag_size 1
+#define        V7TAR_linkname_offset 157
+#define        V7TAR_linkname_size 100
+#define        V7TAR_padding_offset 257
+#define        V7TAR_padding_size 255
+
+/*
+ * A filled-in copy of the header for initialization.
+ */
+static const char template_header[] = {
+       /* name: 100 bytes */
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,
+       /* Mode, space-null termination: 8 bytes */
+       '0','0','0','0','0','0', ' ','\0',
+       /* uid, space-null termination: 8 bytes */
+       '0','0','0','0','0','0', ' ','\0',
+       /* gid, space-null termination: 8 bytes */
+       '0','0','0','0','0','0', ' ','\0',
+       /* size, space termation: 12 bytes */
+       '0','0','0','0','0','0','0','0','0','0','0', ' ',
+       /* mtime, space termation: 12 bytes */
+       '0','0','0','0','0','0','0','0','0','0','0', ' ',
+       /* Initial checksum value: 8 spaces */
+       ' ',' ',' ',' ',' ',' ',' ',' ',
+       /* Typeflag: 1 byte */
+       0,
+       /* Linkname: 100 bytes */
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,
+       /* Padding: 255 bytes */
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+       0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0
+};
+
+static ssize_t archive_write_v7tar_data(struct archive_write *a, const void *buff,
+                   size_t s);
+static int     archive_write_v7tar_free(struct archive_write *);
+static int     archive_write_v7tar_close(struct archive_write *);
+static int     archive_write_v7tar_finish_entry(struct archive_write *);
+static int     archive_write_v7tar_header(struct archive_write *,
+                   struct archive_entry *entry);
+static int     archive_write_v7tar_options(struct archive_write *,
+                   const char *, const char *);
+static int     format_256(int64_t, char *, int);
+static int     format_number(int64_t, char *, int size, int max, int strict);
+static int     format_octal(int64_t, char *, int);
+static int     format_header_v7tar(struct archive_write *, char h[512],
+                   struct archive_entry *, int, struct archive_string_conv *);
+
+/*
+ * Set output format to 'v7tar' format.
+ */
+int
+archive_write_set_format_v7tar(struct archive *_a)
+{
+       struct archive_write *a = (struct archive_write *)_a;
+       struct v7tar *v7tar;
+
+       archive_check_magic(_a, ARCHIVE_WRITE_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_write_set_format_v7tar");
+
+       /* If someone else was already registered, unregister them. */
+       if (a->format_free != NULL)
+               (a->format_free)(a);
+
+       /* Basic internal sanity test. */
+       if (sizeof(template_header) != 512) {
+               archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                   "Internal: template_header wrong size: %zu should be 512",
+                   sizeof(template_header));
+               return (ARCHIVE_FATAL);
+       }
+
+       v7tar = (struct v7tar *)malloc(sizeof(*v7tar));
+       if (v7tar == NULL) {
+               archive_set_error(&a->archive, ENOMEM,
+                   "Can't allocate v7tar data");
+               return (ARCHIVE_FATAL);
+       }
+       memset(v7tar, 0, sizeof(*v7tar));
+       a->format_data = v7tar;
+       a->format_name = "tar (non-POSIX)";
+       a->format_options = archive_write_v7tar_options;
+       a->format_write_header = archive_write_v7tar_header;
+       a->format_write_data = archive_write_v7tar_data;
+       a->format_close = archive_write_v7tar_close;
+       a->format_free = archive_write_v7tar_free;
+       a->format_finish_entry = archive_write_v7tar_finish_entry;
+       a->archive.archive_format = ARCHIVE_FORMAT_TAR;
+       a->archive.archive_format_name = "tar (non-POSIX)";
+       return (ARCHIVE_OK);
+}
+
+static int
+archive_write_v7tar_options(struct archive_write *a, const char *key,
+    const char *val)
+{
+       struct v7tar *v7tar = (struct v7tar *)a->format_data;
+       int ret = ARCHIVE_FAILED;
+
+       if (strcmp(key, "hdrcharset")  == 0) {
+               if (val == NULL || val[0] == 0)
+                       archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                           "%s: hdrcharset option needs a character-set name",
+                           a->format_name);
+               else {
+                       v7tar->opt_sconv = archive_string_conversion_to_charset(
+                           &a->archive, val, 0);
+                       if (v7tar->opt_sconv != NULL)
+                               ret = ARCHIVE_OK;
+                       else
+                               ret = ARCHIVE_FATAL;
+               }
+               return (ret);
+       }
+
+       /* Note: The "warn" return is just to inform the options
+        * supervisor that we didn't handle it.  It will generate
+        * a suitable error if no one used this option. */
+       return (ARCHIVE_WARN);
+}
+
+static int
+archive_write_v7tar_header(struct archive_write *a, struct archive_entry *entry)
+{
+       char buff[512];
+       int ret, ret2;
+       struct v7tar *v7tar;
+       struct archive_entry *entry_main;
+       struct archive_string_conv *sconv;
+
+       v7tar = (struct v7tar *)a->format_data;
+
+       /* Setup default string conversion. */
+       if (v7tar->opt_sconv == NULL) {
+               if (!v7tar->init_default_conversion) {
+                       v7tar->sconv_default =
+                           archive_string_default_conversion_for_write(
+                               &(a->archive));
+                       v7tar->init_default_conversion = 1;
+               }
+               sconv = v7tar->sconv_default;
+       } else
+               sconv = v7tar->opt_sconv;
+
+       /* Sanity check. */
+       if (archive_entry_pathname(entry) == NULL) {
+               archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+                   "Can't record entry in tar file without pathname");
+               return (ARCHIVE_FAILED);
+       }
+
+       /* Only regular files (not hardlinks) have data. */
+       if (archive_entry_hardlink(entry) != NULL ||
+           archive_entry_symlink(entry) != NULL ||
+           !(archive_entry_filetype(entry) == AE_IFREG))
+               archive_entry_set_size(entry, 0);
+
+       if (AE_IFDIR == archive_entry_filetype(entry)) {
+               const char *p;
+               size_t path_length;
+               /*
+                * Ensure a trailing '/'.  Modify the entry so
+                * the client sees the change.
+                */
+#if defined(_WIN32) && !defined(__CYGWIN__)
+               const wchar_t *wp;
+
+               wp = archive_entry_pathname_w(entry);
+               if (wp != NULL && wp[wcslen(wp) -1] != L'/') {
+                       struct archive_wstring ws;
+
+                       archive_string_init(&ws);
+                       path_length = wcslen(wp);
+                       if (archive_wstring_ensure(&ws,
+                           path_length + 2) == NULL) {
+                               archive_set_error(&a->archive, ENOMEM,
+                                   "Can't allocate v7tar data");
+                               archive_wstring_free(&ws);
+                               return(ARCHIVE_FATAL);
+                       }
+                       /* Should we keep '\' ? */
+                       if (wp[path_length -1] == L'\\')
+                               path_length--;
+                       archive_wstrncpy(&ws, wp, path_length);
+                       archive_wstrappend_wchar(&ws, L'/');
+                       archive_entry_copy_pathname_w(entry, ws.s);
+                       archive_wstring_free(&ws);
+                       p = NULL;
+               } else
+#endif
+                       p = archive_entry_pathname(entry);
+               /*
+                * On Windows, this is a backup operation just in
+                * case getting WCS failed. On POSIX, this is a
+                * normal operation.
+                */
+               if (p != NULL && p[strlen(p) - 1] != '/') {
+                       struct archive_string as;
+
+                       archive_string_init(&as);
+                       path_length = strlen(p);
+                       if (archive_string_ensure(&as,
+                           path_length + 2) == NULL) {
+                               archive_set_error(&a->archive, ENOMEM,
+                                   "Can't allocate v7tar data");
+                               archive_string_free(&as);
+                               return(ARCHIVE_FATAL);
+                       }
+#if defined(_WIN32) && !defined(__CYGWIN__)
+                       /* NOTE: This might break the pathname
+                        * if the current code page is CP932 and
+                        * the pathname includes a character '\'
+                        * as a part of its multibyte pathname. */
+                       if (p[strlen(p) -1] == '\\')
+                               path_length--;
+                       else
+#endif
+                       archive_strncpy(&as, p, path_length);
+                       archive_strappend_char(&as, '/');
+                       archive_entry_copy_pathname(entry, as.s);
+                       archive_string_free(&as);
+               }
+       }
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       /* Make sure the path separators in pahtname, hardlink and symlink
+        * are all slash '/', not the Windows path separator '\'. */
+       entry_main = __la_win_entry_in_posix_pathseparator(entry);
+       if (entry_main == NULL) {
+               archive_set_error(&a->archive, ENOMEM,
+                   "Can't allocate v7tar data");
+               return(ARCHIVE_FATAL);
+       }
+       if (entry != entry_main)
+               entry = entry_main;
+       else
+               entry_main = NULL;
+#else
+       entry_main = NULL;
+#endif
+       ret = format_header_v7tar(a, buff, entry, 1, sconv);
+       if (ret < ARCHIVE_WARN) {
+               if (entry_main)
+                       archive_entry_free(entry_main);
+               return (ret);
+       }
+       ret2 = __archive_write_output(a, buff, 512);
+       if (ret2 < ARCHIVE_WARN) {
+               if (entry_main)
+                       archive_entry_free(entry_main);
+               return (ret2);
+       }
+       if (ret2 < ret)
+               ret = ret2;
+
+       v7tar->entry_bytes_remaining = archive_entry_size(entry);
+       v7tar->entry_padding = 0x1ff & (-(int64_t)v7tar->entry_bytes_remaining);
+       if (entry_main)
+               archive_entry_free(entry_main);
+       return (ret);
+}
+
+/*
+ * Format a basic 512-byte "v7tar" header.
+ *
+ * Returns -1 if format failed (due to field overflow).
+ * Note that this always formats as much of the header as possible.
+ * If "strict" is set to zero, it will extend numeric fields as
+ * necessary (overwriting terminators or using base-256 extensions).
+ *
+ */
+static int
+format_header_v7tar(struct archive_write *a, char h[512],
+    struct archive_entry *entry, int strict,
+    struct archive_string_conv *sconv)
+{
+       unsigned int checksum;
+       int i, r, ret;
+       size_t copy_length;
+       const char *p, *pp;
+       int mytartype;
+
+       ret = 0;
+       mytartype = -1;
+       /*
+        * The "template header" already includes the "v7tar"
+        * signature, various end-of-field markers and other required
+        * elements.
+        */
+       memcpy(h, &template_header, 512);
+
+       /*
+        * Because the block is already null-filled, and strings
+        * are allowed to exactly fill their destination (without null),
+        * I use memcpy(dest, src, strlen()) here a lot to copy strings.
+        */
+       r = archive_entry_pathname_l(entry, &pp, &copy_length, sconv);
+       if (r != 0) {
+               if (errno == ENOMEM) {
+                       archive_set_error(&a->archive, ENOMEM,
+                           "Can't allocate memory for Pathname");
+                       return (ARCHIVE_FATAL);
+               }
+               archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                   "Can't translate pathname '%s' to %s",
+                   pp, archive_string_conversion_charset_name(sconv));
+               ret = ARCHIVE_WARN;
+       }
+       if (strict && copy_length < V7TAR_name_size)
+               memcpy(h + V7TAR_name_offset, pp, copy_length);
+       else if (!strict && copy_length <= V7TAR_name_size)
+               memcpy(h + V7TAR_name_offset, pp, copy_length);
+       else {
+               /* Prefix is too long. */
+               archive_set_error(&a->archive, ENAMETOOLONG,
+                   "Pathname too long");
+               ret = ARCHIVE_FAILED;
+       }
+
+       r = archive_entry_hardlink_l(entry, &p, &copy_length, sconv);
+       if (r != 0) {
+               if (errno == ENOMEM) {
+                       archive_set_error(&a->archive, ENOMEM,
+                           "Can't allocate memory for Linkname");
+                       return (ARCHIVE_FATAL);
+               }
+               archive_set_error(&a->archive,
+                   ARCHIVE_ERRNO_FILE_FORMAT,
+                   "Can't translate linkname '%s' to %s",
+                   p, archive_string_conversion_charset_name(sconv));
+               ret = ARCHIVE_WARN;
+       }
+       if (copy_length > 0)
+               mytartype = '1';
+       else {
+               r = archive_entry_symlink_l(entry, &p, &copy_length, sconv);
+               if (r != 0) {
+                       if (errno == ENOMEM) {
+                               archive_set_error(&a->archive, ENOMEM,
+                                   "Can't allocate memory for Linkname");
+                               return (ARCHIVE_FATAL);
+                       }
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_FILE_FORMAT,
+                           "Can't translate linkname '%s' to %s",
+                           p, archive_string_conversion_charset_name(sconv));
+                       ret = ARCHIVE_WARN;
+               }
+       }
+       if (copy_length > 0) {
+               if (copy_length >= V7TAR_linkname_size) {
+                       archive_set_error(&a->archive, ENAMETOOLONG,
+                           "Link contents too long");
+                       ret = ARCHIVE_FAILED;
+                       copy_length = V7TAR_linkname_size;
+               }
+               memcpy(h + V7TAR_linkname_offset, p, copy_length);
+       }
+
+       if (format_number(archive_entry_mode(entry) & 07777,
+           h + V7TAR_mode_offset, V7TAR_mode_size,
+           V7TAR_mode_max_size, strict)) {
+               archive_set_error(&a->archive, ERANGE,
+                   "Numeric mode too large");
+               ret = ARCHIVE_FAILED;
+       }
+
+       if (format_number(archive_entry_uid(entry),
+           h + V7TAR_uid_offset, V7TAR_uid_size, V7TAR_uid_max_size, strict)) {
+               archive_set_error(&a->archive, ERANGE,
+                   "Numeric user ID too large");
+               ret = ARCHIVE_FAILED;
+       }
+
+       if (format_number(archive_entry_gid(entry),
+           h + V7TAR_gid_offset, V7TAR_gid_size, V7TAR_gid_max_size, strict)) {
+               archive_set_error(&a->archive, ERANGE,
+                   "Numeric group ID too large");
+               ret = ARCHIVE_FAILED;
+       }
+
+       if (format_number(archive_entry_size(entry),
+           h + V7TAR_size_offset, V7TAR_size_size,
+           V7TAR_size_max_size, strict)) {
+               archive_set_error(&a->archive, ERANGE,
+                   "File size out of range");
+               ret = ARCHIVE_FAILED;
+       }
+
+       if (format_number(archive_entry_mtime(entry),
+           h + V7TAR_mtime_offset, V7TAR_mtime_size,
+           V7TAR_mtime_max_size, strict)) {
+               archive_set_error(&a->archive, ERANGE,
+                   "File modification time too large");
+               ret = ARCHIVE_FAILED;
+       }
+
+       if (mytartype >= 0) {
+               h[V7TAR_typeflag_offset] = mytartype;
+       } else {
+               switch (archive_entry_filetype(entry)) {
+               case AE_IFREG: case AE_IFDIR:
+                       break;
+               case AE_IFLNK:
+                       h[V7TAR_typeflag_offset] = '2';
+                       break;
+               case AE_IFCHR:
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_FILE_FORMAT,
+                           "tar format cannot archive character device");
+                       return (ARCHIVE_FAILED);
+               case AE_IFBLK:
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_FILE_FORMAT,
+                           "tar format cannot archive block device");
+                       return (ARCHIVE_FAILED);
+               case AE_IFIFO:
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_FILE_FORMAT,
+                           "tar format cannot archive fifo");
+                       return (ARCHIVE_FAILED);
+               case AE_IFSOCK:
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_FILE_FORMAT,
+                           "tar format cannot archive socket");
+                       return (ARCHIVE_FAILED);
+               default:
+                       archive_set_error(&a->archive,
+                           ARCHIVE_ERRNO_FILE_FORMAT,
+                           "tar format cannot archive this (mode=0%lo)",
+                           (unsigned long)archive_entry_mode(entry));
+                       ret = ARCHIVE_FAILED;
+               }
+       }
+
+       checksum = 0;
+       for (i = 0; i < 512; i++)
+               checksum += 255 & (unsigned int)h[i];
+       format_octal(checksum, h + V7TAR_checksum_offset, 6);
+       /* Can't be pre-set in the template. */
+       h[V7TAR_checksum_offset + 6] = '\0';
+       return (ret);
+}
+
+/*
+ * Format a number into a field, with some intelligence.
+ */
+static int
+format_number(int64_t v, char *p, int s, int maxsize, int strict)
+{
+       int64_t limit;
+
+       limit = ((int64_t)1 << (s*3));
+
+       /* "Strict" only permits octal values with proper termination. */
+       if (strict)
+               return (format_octal(v, p, s));
+
+       /*
+        * In non-strict mode, we allow the number to overwrite one or
+        * more bytes of the field termination.  Even old tar
+        * implementations should be able to handle this with no
+        * problem.
+        */
+       if (v >= 0) {
+               while (s <= maxsize) {
+                       if (v < limit)
+                               return (format_octal(v, p, s));
+                       s++;
+                       limit <<= 3;
+               }
+       }
+
+       /* Base-256 can handle any number, positive or negative. */
+       return (format_256(v, p, maxsize));
+}
+
+/*
+ * Format a number into the specified field using base-256.
+ */
+static int
+format_256(int64_t v, char *p, int s)
+{
+       p += s;
+       while (s-- > 0) {
+               *--p = (char)(v & 0xff);
+               v >>= 8;
+       }
+       *p |= 0x80; /* Set the base-256 marker bit. */
+       return (0);
+}
+
+/*
+ * Format a number into the specified field.
+ */
+static int
+format_octal(int64_t v, char *p, int s)
+{
+       int len;
+
+       len = s;
+
+       /* Octal values can't be negative, so use 0. */
+       if (v < 0) {
+               while (len-- > 0)
+                       *p++ = '0';
+               return (-1);
+       }
+
+       p += s;         /* Start at the end and work backwards. */
+       while (s-- > 0) {
+               *--p = (char)('0' + (v & 7));
+               v >>= 3;
+       }
+
+       if (v == 0)
+               return (0);
+
+       /* If it overflowed, fill field with max value. */
+       while (len-- > 0)
+               *p++ = '7';
+
+       return (-1);
+}
+
+static int
+archive_write_v7tar_close(struct archive_write *a)
+{
+       return (__archive_write_nulls(a, 512*2));
+}
+
+static int
+archive_write_v7tar_free(struct archive_write *a)
+{
+       struct v7tar *v7tar;
+
+       v7tar = (struct v7tar *)a->format_data;
+       free(v7tar);
+       a->format_data = NULL;
+       return (ARCHIVE_OK);
+}
+
+static int
+archive_write_v7tar_finish_entry(struct archive_write *a)
+{
+       struct v7tar *v7tar;
+       int ret;
+
+       v7tar = (struct v7tar *)a->format_data;
+       ret = __archive_write_nulls(a,
+           (size_t)(v7tar->entry_bytes_remaining + v7tar->entry_padding));
+       v7tar->entry_bytes_remaining = v7tar->entry_padding = 0;
+       return (ret);
+}
+
+static ssize_t
+archive_write_v7tar_data(struct archive_write *a, const void *buff, size_t s)
+{
+       struct v7tar *v7tar;
+       int ret;
+
+       v7tar = (struct v7tar *)a->format_data;
+       if (s > v7tar->entry_bytes_remaining)
+               s = (size_t)v7tar->entry_bytes_remaining;
+       ret = __archive_write_output(a, buff, s);
+       v7tar->entry_bytes_remaining -= s;
+       if (ret != ARCHIVE_OK)
+               return (ret);
+       return (s);
+}
index 99e9a5ccc44b26cef1996e31b68bfaa3e12067cf..efbe7b0898fdfd39c7612c53a580b4ff017b2458 100644 (file)
@@ -193,6 +193,7 @@ IF(ENABLE_TEST)
     test_write_format_tar_empty.c
     test_write_format_tar_sparse.c
     test_write_format_tar_ustar.c
+    test_write_format_tar_v7tar.c
     test_write_format_xar.c
     test_write_format_xar_empty.c
     test_write_format_zip.c
diff --git a/libarchive/test/test_write_format_tar_v7tar.c b/libarchive/test/test_write_format_tar_v7tar.c
new file mode 100644 (file)
index 0000000..1690995
--- /dev/null
@@ -0,0 +1,259 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * Copyright (c) 2012 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 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 int
+is_null(const char *p, size_t l)
+{
+       while (l > 0) {
+               if (*p != '\0')
+                       return (0);
+               --l;
+               ++p;
+       }
+       return (1);
+}
+
+/* Verify the contents, then erase them to NUL bytes. */
+/* Tar requires all "unused" bytes be set to NUL; this allows us
+ * to easily verify that by invoking is_null() over the entire header
+ * after verifying each field. */
+#define myAssertEqualMem(a,b,s) assertEqualMem(a, b, s); memset(a, 0, s)
+
+/*
+ * Detailed verification that 'v7tar' archives are written with
+ * the correct format.
+ */
+DEFINE_TEST(test_write_format_tar_v7tar)
+{
+       struct archive *a;
+       struct archive_entry *entry;
+       char *buff, *e;
+       size_t buffsize = 100000;
+       size_t used;
+       int i;
+       char f99[100];
+       char f100[101];
+
+       for (i = 0; i < 99; ++i)
+               f99[i] = 'a' + i % 26;
+       f99[99] = '\0';
+
+       for (i = 0; i < 100; ++i)
+               f100[i] = 'A' + i % 26;
+       f100[100] = '\0';
+
+       buff = malloc(buffsize);
+
+       /* Create a new archive in memory. */
+       assert((a = archive_write_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_write_set_format_v7tar(a));
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_write_add_filter_none(a));
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_write_open_memory(a, buff, buffsize, &used));
+
+       /*
+        * Add various files to it.
+        * TODO: Extend this to cover more filetypes.
+        */
+
+       /* "file" with 10 bytes of content */
+       assert((entry = archive_entry_new()) != NULL);
+       archive_entry_set_mtime(entry, 1, 10);
+       archive_entry_set_pathname(entry, "file");
+       archive_entry_set_mode(entry, S_IFREG | 0664);
+       archive_entry_set_size(entry, 10);
+       archive_entry_set_uid(entry, 80);
+       archive_entry_set_gid(entry, 90);
+       archive_entry_set_dev(entry, 12);
+       archive_entry_set_ino(entry, 89);
+       archive_entry_set_nlink(entry, 2);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, entry));
+       archive_entry_free(entry);
+       assertEqualIntA(a, 10, archive_write_data(a, "1234567890", 10));
+
+       /* Hardlink to "file" with 10 bytes of content */
+       assert((entry = archive_entry_new()) != NULL);
+       archive_entry_set_mtime(entry, 1, 10);
+       archive_entry_set_pathname(entry, "linkfile");
+       archive_entry_set_hardlink(entry, "file");
+       archive_entry_set_mode(entry, S_IFREG | 0664);
+       archive_entry_set_size(entry, 10);
+       archive_entry_set_uid(entry, 80);
+       archive_entry_set_gid(entry, 90);
+       archive_entry_set_dev(entry, 12);
+       archive_entry_set_ino(entry, 89);
+       archive_entry_set_nlink(entry, 2);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, entry));
+       archive_entry_free(entry);
+       /* Write of data to dir should fail == zero bytes get written. */
+       assertEqualIntA(a, 0, archive_write_data(a, "1234567890", 10));
+
+       /* "dir" */
+       assert((entry = archive_entry_new()) != NULL);
+       archive_entry_set_mtime(entry, 2, 20);
+       archive_entry_set_pathname(entry, "dir");
+       archive_entry_set_mode(entry, S_IFDIR | 0775);
+       archive_entry_set_size(entry, 10);
+       archive_entry_set_nlink(entry, 2);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, entry));
+       archive_entry_free(entry);
+       /* Write of data to dir should fail == zero bytes get written. */
+       assertEqualIntA(a, 0, archive_write_data(a, "1234567890", 10));
+
+       /* "symlink" pointing to "file" */
+       assert((entry = archive_entry_new()) != NULL);
+       archive_entry_set_mtime(entry, 3, 30);
+       archive_entry_set_pathname(entry, "symlink");
+       archive_entry_set_mode(entry, 0664);
+       archive_entry_set_filetype(entry, AE_IFLNK);
+       archive_entry_set_symlink(entry,"file");
+       archive_entry_set_size(entry, 0);
+       archive_entry_set_uid(entry, 88);
+       archive_entry_set_gid(entry, 98);
+       archive_entry_set_dev(entry, 12);
+       archive_entry_set_ino(entry, 90);
+       archive_entry_set_nlink(entry, 1);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, entry));
+       archive_entry_free(entry);
+
+       /* file with 99-char filename. */
+       assert((entry = archive_entry_new()) != NULL);
+       archive_entry_set_mtime(entry, 1, 10);
+       archive_entry_set_pathname(entry, f99);
+       archive_entry_set_mode(entry, S_IFREG | 0664);
+       archive_entry_set_size(entry, 0);
+       archive_entry_set_uid(entry, 82);
+       archive_entry_set_gid(entry, 93);
+       archive_entry_set_dev(entry, 102);
+       archive_entry_set_ino(entry, 7);
+       archive_entry_set_nlink(entry, 1);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, entry));
+       archive_entry_free(entry);
+
+       /* file with 100-char filename. */
+       assert((entry = archive_entry_new()) != NULL);
+       archive_entry_set_mtime(entry, 1, 10);
+       archive_entry_set_pathname(entry, f100);
+       archive_entry_set_mode(entry, S_IFREG | 0664);
+       archive_entry_set_size(entry, 0);
+       archive_entry_set_uid(entry, 82);
+       archive_entry_set_gid(entry, 93);
+       archive_entry_set_dev(entry, 102);
+       archive_entry_set_ino(entry, 7);
+       archive_entry_set_nlink(entry, 1);
+       failure("100-char filename should be rejected");
+       assertEqualIntA(a, ARCHIVE_FAILED, archive_write_header(a, entry));
+       archive_entry_free(entry);
+
+       /* Close out the archive. */
+       assertEqualInt(ARCHIVE_OK, archive_write_free(a));
+
+       /*
+        * Verify the archive format.
+        */
+       e = buff;
+
+       /* "file" */
+       myAssertEqualMem(e + 0, "file", 5); /* Filename */
+       myAssertEqualMem(e + 100, "000664 ", 8); /* mode */
+       myAssertEqualMem(e + 108, "000120 ", 8); /* uid */
+       myAssertEqualMem(e + 116, "000132 ", 8); /* gid */
+       myAssertEqualMem(e + 124, "00000000012 ", 12); /* size */
+       myAssertEqualMem(e + 136, "00000000001 ", 12); /* mtime */
+       myAssertEqualMem(e + 148, "005335\0 ", 8); /* checksum */
+       myAssertEqualMem(e + 156, "", 1); /* linkflag */
+       myAssertEqualMem(e + 157, "", 1); /* linkname */
+       assert(is_null(e + 0, 512));
+       myAssertEqualMem(e + 512, "1234567890", 10);
+       assert(is_null(e + 512, 512));
+       e += 1024;
+
+       /* hardlink to "file" */
+       myAssertEqualMem(e + 0, "linkfile", 9); /* Filename */
+       myAssertEqualMem(e + 100, "000664 ", 8); /* mode */
+       myAssertEqualMem(e + 108, "000120 ", 8); /* uid */
+       myAssertEqualMem(e + 116, "000132 ", 8); /* gid */
+       myAssertEqualMem(e + 124, "00000000000 ", 12); /* size */
+       myAssertEqualMem(e + 136, "00000000001 ", 12); /* mtime */
+       myAssertEqualMem(e + 148, "007131\0 ", 8); /* checksum */
+       myAssertEqualMem(e + 156, "1", 1); /* linkflag */
+       myAssertEqualMem(e + 157, "file", 5); /* linkname */
+       assert(is_null(e + 0, 512));
+       e += 512;
+
+       /* "dir" */
+       myAssertEqualMem(e + 0, "dir/", 4); /* Filename */
+       myAssertEqualMem(e + 100, "000775 ", 8); /* mode */
+       myAssertEqualMem(e + 108, "000000 ", 8); /* uid */
+       myAssertEqualMem(e + 116, "000000 ", 8); /* gid */
+       myAssertEqualMem(e + 124, "00000000000 ", 12); /* size */
+       myAssertEqualMem(e + 136, "00000000002 ", 12); /* mtime */
+       myAssertEqualMem(e + 148, "005243\0 ", 8); /* checksum */
+       myAssertEqualMem(e + 156, "", 1); /* typeflag */
+       myAssertEqualMem(e + 157, "", 1); /* linkname */
+       assert(is_null(e + 0, 512));
+       e += 512;
+
+       /* "symlink" pointing to "file" */
+       myAssertEqualMem(e + 0, "symlink", 8); /* Filename */
+       myAssertEqualMem(e + 100, "000664 ", 8); /* mode */
+       myAssertEqualMem(e + 108, "000130 ", 8); /* uid */
+       myAssertEqualMem(e + 116, "000142 ", 8); /* gid */
+       myAssertEqualMem(e + 124, "00000000000 ", 12); /* size */
+       myAssertEqualMem(e + 136, "00000000003 ", 12); /* mtime */
+       myAssertEqualMem(e + 148, "007027\0 ", 8); /* checksum */
+       myAssertEqualMem(e + 156, "2", 1); /* linkflag */
+       myAssertEqualMem(e + 157, "file", 5); /* linkname */
+       assert(is_null(e + 0, 512));
+       e += 512;
+
+       /* File with 99-char filename */
+       myAssertEqualMem(e + 0, f99, 100); /* Filename */
+       myAssertEqualMem(e + 100, "000664 ", 8); /* mode */
+       myAssertEqualMem(e + 108, "000122 ", 8); /* uid */
+       myAssertEqualMem(e + 116, "000135 ", 8); /* gid */
+       myAssertEqualMem(e + 124, "00000000000 ", 12); /* size */
+       myAssertEqualMem(e + 136, "00000000001 ", 12); /* mtime */
+       myAssertEqualMem(e + 148, "031543\0 ", 8); /* checksum */
+       myAssertEqualMem(e + 156, "", 1); /* linkflag */
+       myAssertEqualMem(e + 157, "", 1); /* linkname */
+       assert(is_null(e + 0, 512));
+       e += 512;
+
+       /* TODO: Verify other types of entries. */
+
+       /* Last entry is end-of-archive marker. */
+       assert(is_null(e, 1024));
+       e += 1024;
+
+       assertEqualInt((int)used, e - buff);
+
+       free(buff);
+}