From: Tim Kientzle Date: Sun, 18 Apr 2010 05:56:53 +0000 (-0400) Subject: Basic implementation of a GNU tar-compatible writer. This uses X-Git-Tag: v3.0.0a~1098 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7862cf7aebe15c6940b50490f0172eeea6774b22;p=thirdparty%2Flibarchive.git Basic implementation of a GNU tar-compatible writer. This uses the header conventions used by GNU tar 1.13 and later. In particular: * name and linkname are permitted to fill their respective fields (Earlier GNU tar guaranteed a trailing null) * mode field does not include the file type (Earlier GNU tar included file type bits in the mode field) * Most fields are filled with leading '0' characters (Earlier GNU tar used leading spaces) SVN-Revision: 2264 --- diff --git a/Makefile.am b/Makefile.am index 6f4fe9aef..fd4e1f7a1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -168,6 +168,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_gnutar.c \ libarchive/archive_write_set_format_zip.c \ libarchive/archive_write_set_options.c \ libarchive/config_freebsd.h \ @@ -329,6 +330,7 @@ libarchive_test_SOURCES= \ libarchive/test/test_write_format_cpio_empty.c \ libarchive/test/test_write_format_cpio_odc.c \ libarchive/test/test_write_format_cpio_newc.c \ + libarchive/test/test_write_format_gnutar.c \ libarchive/test/test_write_format_iso9660.c \ libarchive/test/test_write_format_iso9660_boot.c \ libarchive/test/test_write_format_iso9660_empty.c \ diff --git a/libarchive/CMakeLists.txt b/libarchive/CMakeLists.txt index 556baf83f..139f30b35 100644 --- a/libarchive/CMakeLists.txt +++ b/libarchive/CMakeLists.txt @@ -83,6 +83,7 @@ SET(libarchive_SOURCES archive_write_set_format_by_name.c archive_write_set_format_cpio.c archive_write_set_format_cpio_newc.c + archive_write_set_format_gnutar.c archive_write_set_format_iso9660.c archive_write_set_format_mtree.c archive_write_set_format_pax.c diff --git a/libarchive/archive.h b/libarchive/archive.h index 84f9ea147..b06200cb6 100644 --- a/libarchive/archive.h +++ b/libarchive/archive.h @@ -578,6 +578,7 @@ __LA_DECL int archive_write_set_format_ar_bsd(struct archive *); __LA_DECL int archive_write_set_format_ar_svr4(struct archive *); __LA_DECL int archive_write_set_format_cpio(struct archive *); __LA_DECL int archive_write_set_format_cpio_newc(struct archive *); +__LA_DECL int archive_write_set_format_gnutar(struct archive *); __LA_DECL int archive_write_set_format_iso9660(struct archive *); __LA_DECL int archive_write_set_format_mtree(struct archive *); /* TODO: int archive_write_set_format_old_tar(struct archive *); */ diff --git a/libarchive/archive_write_set_format.c b/libarchive/archive_write_set_format.c index dc0b76406..ac348f83b 100644 --- a/libarchive/archive_write_set_format.c +++ b/libarchive/archive_write_set_format.c @@ -42,14 +42,15 @@ static struct { int code; int (*setter)(struct archive *); } codes[] = { { ARCHIVE_FORMAT_CPIO, archive_write_set_format_cpio }, - { ARCHIVE_FORMAT_CPIO_SVR4_NOCRC, archive_write_set_format_cpio_newc }, { ARCHIVE_FORMAT_CPIO_POSIX, archive_write_set_format_cpio }, + { ARCHIVE_FORMAT_CPIO_SVR4_NOCRC, archive_write_set_format_cpio_newc }, { ARCHIVE_FORMAT_ISO9660, archive_write_set_format_iso9660 }, { ARCHIVE_FORMAT_MTREE, archive_write_set_format_mtree }, { ARCHIVE_FORMAT_SHAR, archive_write_set_format_shar }, { ARCHIVE_FORMAT_SHAR_BASE, archive_write_set_format_shar }, { ARCHIVE_FORMAT_SHAR_DUMP, archive_write_set_format_shar_dump }, { ARCHIVE_FORMAT_TAR, archive_write_set_format_pax_restricted }, + { ARCHIVE_FORMAT_TAR_GNUTAR, archive_write_set_format_gnutar }, { ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE, archive_write_set_format_pax }, { ARCHIVE_FORMAT_TAR_PAX_RESTRICTED, archive_write_set_format_pax_restricted }, diff --git a/libarchive/archive_write_set_format_by_name.c b/libarchive/archive_write_set_format_by_name.c index efe9c521d..a78d93a6f 100644 --- a/libarchive/archive_write_set_format_by_name.c +++ b/libarchive/archive_write_set_format_by_name.c @@ -51,6 +51,7 @@ struct { const char *name; int (*setter)(struct archive *); } names[] = { "bsdtar", archive_write_set_format_pax_restricted }, { "cd9660", archive_write_set_format_iso9660 }, { "cpio", archive_write_set_format_cpio }, + { "gnutar", archive_write_set_format_gnutar }, { "iso", archive_write_set_format_iso9660 }, { "iso9660", archive_write_set_format_iso9660 }, { "mtree", archive_write_set_format_mtree }, diff --git a/libarchive/archive_write_set_format_gnutar.c b/libarchive/archive_write_set_format_gnutar.c new file mode 100644 index 000000000..2b45bae95 --- /dev/null +++ b/libarchive/archive_write_set_format_gnutar.c @@ -0,0 +1,529 @@ +/*- + * Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). + * Author: Jonas Gastal + * + * 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: head/lib/libarchive/archive_write_set_format_gnu_tar.c 191579 2009-04-27 18:35:03Z gastal $"); + + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +struct gnutar { + uint64_t entry_bytes_remaining; + uint64_t entry_padding; +}; + +/* + * Define structure of GNU tar header. + */ +#define GNUTAR_name_offset 0 +#define GNUTAR_name_size 100 +#define GNUTAR_mode_offset 100 +#define GNUTAR_mode_size 7 +#define GNUTAR_mode_max_size 8 +#define GNUTAR_uid_offset 108 +#define GNUTAR_uid_size 7 +#define GNUTAR_uid_max_size 8 +#define GNUTAR_gid_offset 116 +#define GNUTAR_gid_size 7 +#define GNUTAR_gid_max_size 8 +#define GNUTAR_size_offset 124 +#define GNUTAR_size_size 11 +#define GNUTAR_size_max_size 12 +#define GNUTAR_mtime_offset 136 +#define GNUTAR_mtime_size 11 +#define GNUTAR_mtime_max_size 11 +#define GNUTAR_checksum_offset 148 +#define GNUTAR_checksum_size 8 +#define GNUTAR_typeflag_offset 156 +#define GNUTAR_typeflag_size 1 +#define GNUTAR_linkname_offset 157 +#define GNUTAR_linkname_size 100 +#define GNUTAR_magic_offset 257 +#define GNUTAR_magic_size 6 +#define GNUTAR_version_offset 263 +#define GNUTAR_version_size 2 +#define GNUTAR_uname_offset 265 +#define GNUTAR_uname_size 32 +#define GNUTAR_gname_offset 297 +#define GNUTAR_gname_size 32 +#define GNUTAR_rdevmajor_offset 329 +#define GNUTAR_rdevmajor_size 6 +#define GNUTAR_rdevmajor_max_size 8 +#define GNUTAR_rdevminor_offset 337 +#define GNUTAR_rdevminor_size 6 +#define GNUTAR_rdevminor_max_size 8 + +/* + * 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, null termination: 8 bytes */ + '0','0','0','0','0','0', '0','\0', + /* uid, null termination: 8 bytes */ + '0','0','0','0','0','0', '0','\0', + /* gid, null termination: 8 bytes */ + '0','0','0','0','0','0', '0','\0', + /* size, space termation: 12 bytes */ + '0','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', '\0', + /* Initial checksum value: 8 spaces */ + ' ',' ',' ',' ',' ',' ',' ',' ', + /* Typeflag: 1 byte */ + '0', /* '0' = regular file */ + /* 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, + /* Magic: 8 bytes */ + 'u','s','t','a','r',' ', ' ','\0', + /* Uname: 32 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, + /* Gname: 32 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, + /* rdevmajor + null padding: 8 bytes */ + '\0','\0','\0','\0','\0','\0', '\0','\0', + /* rdevminor + null padding: 8 bytes */ + '\0','\0','\0','\0','\0','\0', '\0','\0', + /* Padding: 167 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 +}; + +static int archive_format_gnutar_header(struct archive_write *, char h[512], + struct archive_entry *, int tartype); +static int archive_write_gnutar_header(struct archive_write *, + struct archive_entry *entry); +static ssize_t archive_write_gnutar_data(struct archive_write *a, const void *buff, + size_t s); +static int archive_write_gnutar_free(struct archive_write *); +static int archive_write_gnutar_close(struct archive_write *); +static int archive_write_gnutar_finish_entry(struct archive_write *); +static int format_256(int64_t, char *, int); +static int format_number(int64_t, char *, int size, int maxsize); +static int format_octal(int64_t, char *, int); + +/* + * Set output format to 'GNU tar' format. + */ +int +archive_write_set_format_gnutar(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct gnutar *gnutar; + + gnutar = (struct gnutar *)calloc(1, sizeof(*gnutar)); + if (gnutar == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate gnutar data"); + return (ARCHIVE_FATAL); + } + a->format_data = gnutar; + a->format_name = "GNU tar"; + a->format_write_header = archive_write_gnutar_header; + a->format_write_data = archive_write_gnutar_data; + a->format_close = archive_write_gnutar_close; + a->format_free = archive_write_gnutar_free; + a->format_finish_entry = archive_write_gnutar_finish_entry; + a->archive.archive_format = ARCHIVE_FORMAT_TAR_GNUTAR; + a->archive.archive_format_name = "GNU tar"; + return (ARCHIVE_OK); +} + +static int +archive_write_gnutar_close(struct archive_write *a) +{ + return (__archive_write_nulls(a, 512*2)); +} + +static int +archive_write_gnutar_free(struct archive_write *a) +{ + struct gnutar *gnutar; + + gnutar = (struct gnutar *)a->format_data; + free(gnutar); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_gnutar_finish_entry(struct archive_write *a) +{ + struct gnutar *gnutar; + int ret; + + gnutar = (struct gnutar *)a->format_data; + ret = __archive_write_nulls(a, + gnutar->entry_bytes_remaining + gnutar->entry_padding); + gnutar->entry_bytes_remaining = gnutar->entry_padding = 0; + return (ret); +} + +static ssize_t +archive_write_gnutar_data(struct archive_write *a, const void *buff, size_t s) +{ + struct gnutar *gnutar; + int ret; + + gnutar = (struct gnutar *)a->format_data; + if (s > gnutar->entry_bytes_remaining) + s = gnutar->entry_bytes_remaining; + ret = __archive_write_output(a, buff, s); + gnutar->entry_bytes_remaining -= s; + if (ret != ARCHIVE_OK) + return (ret); + return (s); +} + +static int +archive_write_gnutar_header(struct archive_write *a, struct archive_entry *entry) +{ + char buff[512]; + int ret, ret2; + int tartype; + const char *linkname; + struct gnutar *gnutar; + + gnutar = (struct gnutar *)a->format_data; + + /* 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; + char *t; + /* + * Ensure a trailing '/'. Modify the entry so + * the client sees the change. + */ + p = archive_entry_pathname(entry); + if (p[strlen(p) - 1] != '/') { + t = (char *)malloc(strlen(p) + 2); + if (t == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate gnutar data"); + return(ARCHIVE_FATAL); + } + strcpy(t, p); + strcat(t, "/"); + archive_entry_copy_pathname(entry, t); + free(t); + } + } + + /* If linkname is longer than 100 chars we need to add a 'K' header. */ + linkname = archive_entry_hardlink(entry); + if (linkname == NULL) + linkname = archive_entry_symlink(entry); + if (linkname != NULL && strlen(linkname) > GNUTAR_linkname_size) { + size_t todo = strlen(linkname); + struct archive_entry *temp = archive_entry_new(); + + /* Uname/gname here don't really matter since noone reads them; + * these are the values that GNU tar happens to use on FreeBSD. */ + archive_entry_set_uname(temp, "root"); + archive_entry_set_gname(temp, "wheel"); + + archive_entry_set_pathname(temp, "././@LongLink"); + archive_entry_set_size(temp, strlen(linkname) + 1); + ret = archive_format_gnutar_header(a, buff, temp, 'K'); + if (ret < ARCHIVE_WARN) + return (ret); + ret = __archive_write_output(a, buff, 512); + if(ret < ARCHIVE_WARN) + return (ret); + archive_entry_free(temp); + /* Write as many 512 bytes blocks as needed to write full name. */ + ret = __archive_write_output(a, linkname, todo); + if(ret < ARCHIVE_WARN) + return (ret); + ret = __archive_write_nulls(a, 0x1ff & (-todo)); + if (ret < ARCHIVE_WARN) + return (ret); + } + + /* If pathname is longer than 100 chars we need to add an 'L' header. */ + if (strlen(archive_entry_pathname(entry)) > GNUTAR_name_size) { + const char *pathname = archive_entry_pathname(entry); + size_t todo = strlen(pathname); + struct archive_entry *temp = archive_entry_new(); + + /* Uname/gname here don't really matter since noone reads them; + * these are the values that GNU tar happens to use on FreeBSD. */ + archive_entry_set_uname(temp, "root"); + archive_entry_set_gname(temp, "wheel"); + + archive_entry_set_pathname(temp, "././@LongLink"); + archive_entry_set_size(temp, strlen(archive_entry_pathname(entry)) + 1); + ret = archive_format_gnutar_header(a, buff, temp, 'L'); + if (ret < ARCHIVE_WARN) + return (ret); + ret = __archive_write_output(a, buff, 512); + if(ret < ARCHIVE_WARN) + return (ret); + archive_entry_free(temp); + /* Write as many 512 bytes blocks as needed to write full name. */ + ret = __archive_write_output(a, pathname, todo); + if(ret < ARCHIVE_WARN) + return (ret); + ret = __archive_write_nulls(a, 0x1ff & (-todo)); + if (ret < ARCHIVE_WARN) + return (ret); + } + + if (archive_entry_hardlink(entry) != NULL) { + tartype = '1'; + } else + switch (archive_entry_filetype(entry)) { + case AE_IFREG: tartype = '0' ; break; + case AE_IFLNK: tartype = '2' ; break; + case AE_IFCHR: tartype = '3' ; break; + case AE_IFBLK: tartype = '4' ; break; + case AE_IFDIR: tartype = '5' ; break; + case AE_IFIFO: tartype = '6' ; break; + 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; + } + + ret = archive_format_gnutar_header(a, buff, entry, tartype); + if (ret < ARCHIVE_WARN) + return (ret); + ret = __archive_write_output(a, buff, 512); + if (ret2 < ARCHIVE_WARN) + return (ret2); + if (ret2 < ret) + ret = ret2; + + gnutar->entry_bytes_remaining = archive_entry_size(entry); + gnutar->entry_padding = 0x1ff & (-(int64_t)gnutar->entry_bytes_remaining); + return (ret); +} + +static int +archive_format_gnutar_header(struct archive_write *a, char h[512], + struct archive_entry *entry, int tartype) +{ + unsigned int checksum; + int i, ret; + size_t copy_length; + const char *p; + + ret = 0; + + /* + * The "template header" already includes the 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. + */ + + p = archive_entry_pathname(entry); + copy_length = strlen(p); + if (copy_length > GNUTAR_name_size) + copy_length = GNUTAR_name_size; + memcpy(h + GNUTAR_name_offset, p, copy_length); + + p = archive_entry_hardlink(entry); + if (p == NULL) + p = archive_entry_symlink(entry); + if (p != NULL && p[0] != '\0') { + copy_length = strlen(p); + if (copy_length > GNUTAR_linkname_size) + copy_length = GNUTAR_linkname_size; + memcpy(h + GNUTAR_linkname_offset, p, copy_length); + } + + /* TODO: How does GNU tar handle unames longer than GNUTAR_uname_size? */ + p = archive_entry_uname(entry); + if (p != NULL && p[0] != '\0') { + copy_length = strlen(p); + if (copy_length > GNUTAR_uname_size) + copy_length = GNUTAR_uname_size; + memcpy(h + GNUTAR_uname_offset, p, copy_length); + } + + /* TODO: How does GNU tar handle gnames longer than GNUTAR_gname_size? */ + p = archive_entry_gname(entry); + if (p != NULL && p[0] != '\0') { + copy_length = strlen(p); + if (strlen(p) > GNUTAR_gname_size) + copy_length = GNUTAR_gname_size; + memcpy(h + GNUTAR_gname_offset, p, copy_length); + } + + /* By truncating the mode here, we ensure it always fits. */ + format_octal(archive_entry_mode(entry) & 07777, + h + GNUTAR_mode_offset, GNUTAR_mode_size); + + /* TODO: How does GNU tar handle large UIDs? */ + if (format_octal(archive_entry_uid(entry), h + GNUTAR_uid_offset, GNUTAR_uid_size)) { + archive_set_error(&a->archive, ERANGE, "Numeric user ID %d too large", + archive_entry_uid(entry)); + ret = ARCHIVE_FAILED; + } + + /* TODO: How does GNU tar handle large GIDs? */ + if (format_octal(archive_entry_gid(entry), h + GNUTAR_gid_offset, GNUTAR_gid_size)) { + archive_set_error(&a->archive, ERANGE, "Numeric group ID %d too large", + archive_entry_gid(entry)); + ret = ARCHIVE_FAILED; + } + + /* GNU tar supports base-256 here, so should never overflow. */ + if (format_number(archive_entry_size(entry), h + GNUTAR_size_offset, + GNUTAR_size_size, GNUTAR_size_max_size)) { + archive_set_error(&a->archive, ERANGE, "File size out of range"); + ret = ARCHIVE_FAILED; + } + + /* Shouldn't overflow before 2106, since mtime field is 33 bits. */ + format_octal(archive_entry_mtime(entry), h + GNUTAR_mtime_offset, GNUTAR_mtime_size); + + if (archive_entry_filetype(entry) == AE_IFBLK + || archive_entry_filetype(entry) == AE_IFCHR) { + if (format_octal(archive_entry_rdevmajor(entry), h + GNUTAR_rdevmajor_offset, + GNUTAR_rdevmajor_size)) { + archive_set_error(&a->archive, ERANGE, + "Major device number too large"); + ret = ARCHIVE_FAILED; + } + + if (format_octal(archive_entry_rdevminor(entry), h + GNUTAR_rdevminor_offset, + GNUTAR_rdevminor_size)) { + archive_set_error(&a->archive, ERANGE, + "Minor device number too large"); + ret = ARCHIVE_FAILED; + } + } + + h[GNUTAR_typeflag_offset] = tartype; + + checksum = 0; + for (i = 0; i < 512; i++) + checksum += 255 & (unsigned int)h[i]; + h[GNUTAR_checksum_offset + 6] = '\0'; /* Can't be pre-set in the template. */ + /* h[GNUTAR_checksum_offset + 7] = ' '; */ /* This is pre-set in the template. */ + format_octal(checksum, h + GNUTAR_checksum_offset, 6); + return (ret); +} + +/* + * Format a number into a field, falling back to base-256 if necessary. + */ +static int +format_number(int64_t v, char *p, int s, int maxsize) +{ + int64_t limit = ((int64_t)1 << (s*3)); + + if (v < limit) + return (format_octal(v, p, s)); + 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 using octal. + */ +static int +format_octal(int64_t v, char *p, int s) +{ + int len = s; + + /* Octal values can't be negative, so use 0. */ + if (v < 0) + v = 0; + + 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); +} diff --git a/libarchive/test/CMakeLists.txt b/libarchive/test/CMakeLists.txt index a5aea96e6..6e952f8a3 100644 --- a/libarchive/test/CMakeLists.txt +++ b/libarchive/test/CMakeLists.txt @@ -106,6 +106,7 @@ IF(ENABLE_TEST) test_write_format_cpio_empty.c test_write_format_cpio_odc.c test_write_format_cpio_newc.c + test_write_format_gnutar.c test_write_format_iso9660.c test_write_format_iso9660_boot.c test_write_format_iso9660_empty.c diff --git a/libarchive/test/test_write_format_gnutar.c b/libarchive/test/test_write_format_gnutar.c new file mode 100644 index 000000000..c3e16086a --- /dev/null +++ b/libarchive/test/test_write_format_gnutar.c @@ -0,0 +1,236 @@ +/*- + * Copyright (c) 2010 Tim Kientzle + * 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$"); + +char buff2[64]; + +/* Some names 1026 characters long */ +static const char *longfilename = "abcdefghijklmnopqrstuvwxyz" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890"; + +static const char *longlinkname = "Xabcdefghijklmnopqrstuvwxyz" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890"; + +static const char *longhardlinkname = "Yabcdefghijklmnopqrstuvwxyz" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890"; + + +DEFINE_TEST(test_write_format_gnutar) +{ + size_t buffsize = 1000000; + char *buff; + struct archive_entry *ae; + struct archive *a; + size_t used; + + buff = malloc(buffsize); /* million bytes of work area */ + assert(buff != NULL); + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_gnutar(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_open_memory(a, buff, buffsize, &used)); + + /* + * "file" 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_birthtime(ae, 3, 30); + 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, S_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, "12345678", 9)); + + /* + * A file with a very long name + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, longfilename); + archive_entry_set_mode(ae, S_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)); + + /* + * A hardlink to the above file. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, longhardlinkname); + archive_entry_copy_hardlink(ae, longfilename); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, 8); + assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae)); + archive_entry_free(ae); + + /* + * A symlink to the above file. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, longlinkname); + archive_entry_copy_symlink(ae, longfilename); + archive_entry_set_mode(ae, S_IFLNK | 0755); + assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae)); + archive_entry_free(ae); + + /* TODO: support GNU tar sparse format and test it here. */ + /* See test_write_format_pax for an example of testing sparse files. */ + + /* Close out the archive. */ + assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_write_free(a)); + + /* + * Some basic verification of the low-level format. + */ + + /* Verify GNU tar magic/version fields */ + assertEqualMem(buff + 257, "ustar \0", 8); + + assertEqualInt(14336, used); + + /* + * + * 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)); + assert(!archive_entry_atime_is_set(ae)); + assert(!archive_entry_birthtime_is_set(ae)); + assert(!archive_entry_ctime_is_set(ae)); + assertEqualInt(5, archive_entry_mtime(ae)); + assertEqualInt(0, archive_entry_mtime_nsec(ae)); + assertEqualString("file", archive_entry_pathname(ae)); + assertEqualInt(S_IFREG | 0755, archive_entry_mode(ae)); + assertEqualInt(8, archive_entry_size(ae)); + assertEqualIntA(a, 8, archive_read_data(a, buff2, 10)); + assertEqualMem(buff2, "12345678", 8); + + /* + * Read file with very long name. + */ + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + assertEqualString(longfilename, archive_entry_pathname(ae)); + assertEqualInt(S_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 hardlink. + */ + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + assertEqualString(longhardlinkname, archive_entry_pathname(ae)); + assertEqualString(longfilename, archive_entry_hardlink(ae)); + + /* + * Read symlink. + */ + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + assertEqualString(longlinkname, archive_entry_pathname(ae)); + assertEqualString(longfilename, archive_entry_symlink(ae)); + assertEqualInt(AE_IFLNK | 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); +}