summaryrefslogtreecommitdiffstats
path: root/lib/libarchive/archive_read_support_format_ar.c
diff options
context:
space:
mode:
authorkientzle <kientzle@FreeBSD.org>2007-04-03 05:34:36 +0000
committerkientzle <kientzle@FreeBSD.org>2007-04-03 05:34:36 +0000
commitb356476feba52a0a45c8e89ebac5d6724a4e6d78 (patch)
treeee7ab4b960264dddb2ad2c445c0e861e145e0ca2 /lib/libarchive/archive_read_support_format_ar.c
parentba7e53058343ca70fa1013a3af10139eac74e8c4 (diff)
downloadFreeBSD-src-b356476feba52a0a45c8e89ebac5d6724a4e6d78.zip
FreeBSD-src-b356476feba52a0a45c8e89ebac5d6724a4e6d78.tar.gz
'ar' format support for libarchive, contributed by Kai Wang.
Diffstat (limited to 'lib/libarchive/archive_read_support_format_ar.c')
-rw-r--r--lib/libarchive/archive_read_support_format_ar.c585
1 files changed, 585 insertions, 0 deletions
diff --git a/lib/libarchive/archive_read_support_format_ar.c b/lib/libarchive/archive_read_support_format_ar.c
new file mode 100644
index 0000000..280d4bb
--- /dev/null
+++ b/lib/libarchive/archive_read_support_format_ar.c
@@ -0,0 +1,585 @@
+/*-
+ * Copyright (c) 2007 Kai Wang
+ * Copyright (c) 2007 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
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "archive_platform.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#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_private.h"
+#include "archive_read_private.h"
+
+struct ar {
+ int bid;
+ int has_strtab;
+ off_t entry_bytes_remaining;
+ off_t entry_offset;
+ off_t entry_padding;
+ char *strtab;
+};
+
+/*
+ * Define structure of the "ar" header.
+ */
+#define AR_name_offset 0
+#define AR_name_size 16
+#define AR_date_offset 16
+#define AR_date_size 12
+#define AR_uid_offset 28
+#define AR_uid_size 6
+#define AR_gid_offset 34
+#define AR_gid_size 6
+#define AR_mode_offset 40
+#define AR_mode_size 8
+#define AR_size_offset 48
+#define AR_size_size 10
+#define AR_fmag_offset 58
+#define AR_fmag_size 2
+
+/*
+ * "ar" magic numbers.
+ */
+#define ARMAG "!<arch>\n"
+#define SARMAG 8 /* strlen(ARMAG); */
+#define AR_EFMT1 "#1/"
+#define SAR_EFMT1 3 /* strlen(AR_EFMT1); */
+#define ARFMAG "`\n"
+#define SARFMAG 2 /* strlen(ARFMAG); */
+
+#define isdigit(x) (x) >= '0' && (x) <= '9'
+
+static int archive_read_format_ar_bid(struct archive_read *a);
+static int archive_read_format_ar_cleanup(struct archive_read *a);
+static int archive_read_format_ar_read_data(struct archive_read *a,
+ const void **buff, size_t *size, off_t *offset);
+static int archive_read_format_ar_skip(struct archive_read *a);
+static int archive_read_format_ar_read_header(struct archive_read *a,
+ struct archive_entry *e);
+static int64_t ar_atol8(const char *p, unsigned char_cnt);
+static int64_t ar_atol10(const char *p, unsigned char_cnt);
+static int ar_parse_string_table(struct archive_read *, struct ar *,
+ const void *, ssize_t);
+
+/*
+ * ANSI C99 defines constants for these, but not everyone supports
+ * those constants, so I define a couple of static variables here and
+ * compute the values. These calculations should be portable to any
+ * 2s-complement architecture.
+ */
+#ifdef UINT64_MAX
+static const uint64_t max_uint64 = UINT64_MAX;
+#else
+static const uint64_t max_uint64 = ~(uint64_t)0;
+#endif
+#ifdef INT64_MAX
+static const int64_t max_int64 = INT64_MAX;
+#else
+static const int64_t max_int64 = (int64_t)((~(uint64_t)0) >> 1);
+#endif
+#ifdef INT64_MIN
+static const int64_t min_int64 = INT64_MIN;
+#else
+static const int64_t min_int64 = (int64_t)(~((~(uint64_t)0) >> 1));
+#endif
+
+int
+archive_read_support_format_ar(struct archive *_a)
+{
+ struct archive_read *a = (struct archive_read *)_a;
+ struct ar *ar;
+ int r;
+
+ ar = (struct ar *)malloc(sizeof(*ar));
+ if (ar == NULL) {
+ archive_set_error(&a->archive, ENOMEM,
+ "Can't allocate ar data");
+ return (ARCHIVE_FATAL);
+ }
+ memset(ar, 0, sizeof(*ar));
+ ar->bid = -1;
+
+ r = __archive_read_register_format(a,
+ ar,
+ archive_read_format_ar_bid,
+ archive_read_format_ar_read_header,
+ archive_read_format_ar_read_data,
+ archive_read_format_ar_skip,
+ archive_read_format_ar_cleanup);
+
+ if (r != ARCHIVE_OK) {
+ free(ar);
+ return (r);
+ }
+ return (ARCHIVE_OK);
+}
+
+static int
+archive_read_format_ar_cleanup(struct archive_read *a)
+{
+ struct ar *ar;
+
+ ar = (struct ar *)*(a->pformat_data);
+ if (ar->has_strtab > 0)
+ free(ar->strtab);
+ free(ar);
+ *(a->pformat_data) = NULL;
+ return (ARCHIVE_OK);
+}
+
+static int
+archive_read_format_ar_bid(struct archive_read *a)
+{
+ struct ar *ar;
+ ssize_t bytes_read;
+ const void *h;
+
+ if (a->archive.archive_format != 0 &&
+ (a->archive.archive_format & ARCHIVE_FORMAT_BASE_MASK) !=
+ ARCHIVE_FORMAT_AR)
+ return(0);
+
+ ar = (struct ar *)*(a->pformat_data);
+
+ if (ar->bid > 0)
+ return (ar->bid);
+
+ bytes_read = (a->compression_read_ahead)(a, &h, SARMAG);
+ if (bytes_read < SARMAG)
+ return (-1);
+
+ /*
+ * Verify the global header.
+ * TODO: Do we need to check more than this?
+ */
+ if (strncmp((const char*)h, ARMAG, SARMAG) == 0) {
+ ar->bid = SARMAG;
+ return (ar->bid);
+ }
+ return (-1);
+}
+
+static int
+archive_read_format_ar_read_header(struct archive_read *a,
+ struct archive_entry *entry)
+{
+ int r, bsd_append;
+ ssize_t bytes;
+ int64_t nval;
+ char *fname, *p;
+ struct ar *ar;
+ const void *b;
+ const char *h;
+
+ bsd_append = 0;
+
+ if (!a->archive.archive_format) {
+ a->archive.archive_format = ARCHIVE_FORMAT_AR;
+ a->archive.archive_format_name = "Unix Archiver";
+ }
+
+ if (a->archive.file_position == 0) {
+ /*
+ * We are now at the beginning of the archive,
+ * so we need first consume the ar global header.
+ */
+ (a->compression_read_consume)(a, SARMAG);
+ }
+
+ /* Read 60-byte header */
+ bytes = (a->compression_read_ahead)(a, &b, 60);
+ if (bytes < 60) {
+ /*
+ * We just encountered an incomplete ar file,
+ * though the _bid function accepted it.
+ */
+ return (ARCHIVE_EOF);
+ }
+ (a->compression_read_consume)(a, 60);
+
+ h = (const char *)b;
+
+ /* Consistency check */
+ if (strncmp(h + AR_fmag_offset, ARFMAG, SARFMAG) != 0) {
+ archive_set_error(&a->archive, EINVAL,
+ "Consistency check failed");
+ return (ARCHIVE_WARN);
+ }
+
+ ar = (struct ar*)*(a->pformat_data);
+
+ if (strncmp(h + AR_name_offset, "//", 2) == 0) {
+ /*
+ * An archive member with ar_name "//" is an archive
+ * string table.
+ */
+ nval = ar_atol10(h + AR_size_offset, AR_size_size);
+ bytes = (a->compression_read_ahead)(a, &b, nval);
+ if (bytes <= 0)
+ return (ARCHIVE_FATAL);
+ if (bytes < nval) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Truncated input file");
+ return (ARCHIVE_FATAL);
+ }
+
+ r = ar_parse_string_table(a, ar, b, nval);
+ if (r == ARCHIVE_OK) {
+ /*
+ * Archive string table only have ar_name and ar_size fileds
+ * in its header.
+ */
+ archive_entry_copy_pathname(entry, "//");
+ nval = ar_atol10(h + AR_size_offset, AR_size_size);
+ archive_entry_set_size(entry, nval);
+
+ ar->entry_offset = 0;
+ ar->entry_bytes_remaining = nval;
+ ar->entry_padding = ar->entry_bytes_remaining % 2;
+ }
+ return (r);
+ }
+
+ if (h[AR_name_offset] == '/' && isdigit(h[AR_name_offset + 1])) {
+ /*
+ * Archive member is common format with SVR4/GNU variant.
+ * "/" followed by one or more digit(s) in the ar_name
+ * filed indicates an index to the string table.
+ */
+ if (ar->has_strtab > 0) {
+ nval = ar_atol10(h + AR_name_offset + 1,
+ AR_name_size - 1);
+ archive_entry_copy_pathname(entry, &ar->strtab[nval]);
+ } else {
+ archive_set_error(&a->archive, EINVAL,
+ "String table does not exist");
+ return (ARCHIVE_WARN);
+ }
+ goto remain;
+ }
+
+ if (strncmp(h + AR_name_offset, AR_EFMT1, SAR_EFMT1) == 0) {
+ /*
+ * Archive member is common format with BSD variant.
+ * AR_EFMT1 is followed by one or more digit(s) indicating
+ * the length of the real filename which is appended
+ * to the header.
+ */
+ nval = ar_atol10(h + AR_name_offset + SAR_EFMT1,
+ AR_name_size - SAR_EFMT1);
+ bytes = (a->compression_read_ahead)(a, &b, nval);
+ if (bytes <= 0)
+ return (ARCHIVE_FATAL);
+ if (bytes < nval) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Truncated input file");
+ return (ARCHIVE_FATAL);
+ }
+
+ (a->compression_read_consume)(a, nval);
+
+ fname = (char *)malloc(nval + 1);
+ if (fname == NULL) {
+ archive_set_error(&a->archive, ENOMEM,
+ "Can't allocate fname buffer");
+ return (ARCHIVE_FATAL);
+ }
+ strncpy(fname, b, nval);
+ fname[nval] = '\0';
+ archive_entry_copy_pathname(entry, fname);
+ free(fname);
+ fname = NULL;
+
+ bsd_append = nval;
+ goto remain;
+ }
+
+ /*
+ * "/" followed by one or more spaces indicate a
+ * SVR4/GNU archive symbol table.
+ *
+ */
+ if (strncmp(h + AR_name_offset, "/ ", 2) == 0) {
+ archive_entry_copy_pathname(entry, "/");
+ goto remain;
+ }
+ /*
+ * "__.SYMDEF" indicates a BSD archive symbol table.
+ */
+ if (strncmp(h + AR_name_offset, "__.SYMDEF", 9) == 0) {
+ archive_entry_copy_pathname(entry, "__.SYMDEF");
+ goto remain;
+ }
+
+ /*
+ * Otherwise, the ar_name fields stores the real
+ * filename.
+ * SVR4/GNU variant append a '/' to mark the end of
+ * filename, while BSD variant use a space.
+ */
+ fname = (char *)malloc(AR_name_size + 1);
+ strncpy(fname, h + AR_name_offset, AR_name_size);
+ fname[AR_name_size] = '\0';
+
+ if ((p = strchr(fname, '/')) != NULL) {
+ /* SVR4/GNU format */
+ *p = '\0';
+ archive_entry_copy_pathname(entry, fname);
+ free(fname);
+ fname = NULL;
+ goto remain;
+ }
+
+ /* BSD format */
+ if ((p = strchr(fname, ' ')) != NULL)
+ *p = '\0';
+ archive_entry_copy_pathname(entry, fname);
+ free(fname);
+ fname = NULL;
+
+remain:
+ /* Copy remaining header */
+ archive_entry_set_mtime(entry,
+ ar_atol10(h + AR_date_offset, AR_date_size), 0);
+ archive_entry_set_uid(entry,
+ ar_atol10(h + AR_uid_offset, AR_uid_size));
+ archive_entry_set_gid(entry,
+ ar_atol10(h + AR_gid_offset, AR_gid_size));
+ archive_entry_set_mode(entry,
+ ar_atol8(h + AR_mode_offset, AR_mode_size));
+ nval = ar_atol10(h + AR_size_offset, AR_size_size);
+
+ ar->entry_offset = 0;
+ ar->entry_padding = nval % 2;
+
+ /*
+ * For BSD variant, we should subtract the length of
+ * the appended filename string from ar_size to get the
+ * real file size. But remember we should do this only
+ * after we had calculated the padding.
+ */
+ if (bsd_append > 0)
+ nval -= bsd_append;
+
+ archive_entry_set_size(entry, nval);
+ ar->entry_bytes_remaining = nval;
+
+ return (ARCHIVE_OK);
+}
+
+static int
+archive_read_format_ar_read_data(struct archive_read *a,
+ const void **buff, size_t *size, off_t *offset)
+{
+ ssize_t bytes_read;
+ struct ar *ar;
+
+ ar = (struct ar *)*(a->pformat_data);
+
+ if (ar->entry_bytes_remaining > 0) {
+ bytes_read = (a->compression_read_ahead)(a, buff, 1);
+ if (bytes_read == 0) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Truncated ar archive");
+ return (ARCHIVE_FATAL);
+ }
+ if (bytes_read < 0)
+ return (ARCHIVE_FATAL);
+ if (bytes_read > ar->entry_bytes_remaining)
+ bytes_read = ar->entry_bytes_remaining;
+ *size = bytes_read;
+ *offset = ar->entry_offset;
+ ar->entry_offset += bytes_read;
+ ar->entry_bytes_remaining -= bytes_read;
+ (a->compression_read_consume)(a, bytes_read);
+ return (ARCHIVE_OK);
+ } else {
+ while (ar->entry_padding > 0) {
+ bytes_read = (a->compression_read_ahead)(a, buff, 1);
+ if (bytes_read <= 0)
+ return (ARCHIVE_FATAL);
+ if (bytes_read > ar->entry_padding)
+ bytes_read = ar->entry_padding;
+ (a->compression_read_consume)(a, bytes_read);
+ ar->entry_padding -= bytes_read;
+ }
+ *buff = NULL;
+ *size = 0;
+ *offset = ar->entry_offset;
+ return (ARCHIVE_EOF);
+ }
+}
+
+static int
+archive_read_format_ar_skip(struct archive_read *a)
+{
+ off_t bytes_skipped;
+ struct ar* ar;
+ int r = ARCHIVE_OK;
+ const void *b; /* Dummy variables */
+ size_t s;
+ off_t o;
+
+ ar = (struct ar *)*(a->pformat_data);
+ if (a->compression_skip == NULL) {
+ while (r == ARCHIVE_OK)
+ r = archive_read_format_ar_read_data(a, &b, &s, &o);
+ return (r);
+ }
+
+ bytes_skipped = (a->compression_skip)(a, ar->entry_bytes_remaining +
+ ar->entry_padding);
+ if (bytes_skipped < 0)
+ return (ARCHIVE_FATAL);
+
+ ar->entry_bytes_remaining = 0;
+ ar->entry_padding = 0;
+
+ return (ARCHIVE_OK);
+}
+
+static int
+ar_parse_string_table(struct archive_read *a, struct ar *ar,
+ const void *h, ssize_t size)
+{
+ char *p;
+
+ if (ar->has_strtab > 0) {
+ archive_set_error(&a->archive, EINVAL,
+ "More than one string tables exist");
+ return (ARCHIVE_WARN);
+ }
+
+ ar->strtab = (char *)malloc(size);
+ if (ar->strtab == NULL) {
+ archive_set_error(&a->archive, ENOMEM,
+ "Can't allocate string table buffer");
+ return (ARCHIVE_FATAL);
+ }
+
+ (void)memcpy(ar->strtab, h, size);
+ p = ar->strtab;
+ while (p < ar->strtab + size - 1) {
+ if (*p == '/') {
+ *p++ = '\0';
+ if (*p == '\n')
+ *p++ = '\0';
+ else {
+ archive_set_error(&a->archive, EINVAL,
+ "Invalid string table");
+ free(ar->strtab);
+ return (ARCHIVE_WARN);
+ }
+ } else
+ p++;
+ }
+ /*
+ * Sanity check, last two chars must be `/\n' or '\n\n',
+ * depending on whether the string table is padded by a '\n'
+ * (string table produced by GNU ar always has a even size).
+ */
+ if (p != ar->strtab + size && *p != '\n') {
+ archive_set_error(&a->archive, EINVAL,
+ "Invalid string table");
+ free(ar->strtab);
+ return (ARCHIVE_WARN);
+ }
+
+ ar->has_strtab = 1;
+ return (ARCHIVE_OK);
+}
+
+static int64_t
+ar_atol8(const char *p, unsigned char_cnt)
+{
+ int64_t l, limit, last_digit_limit;
+ int digit, sign, base;
+
+ base = 8;
+ limit = max_int64 / base;
+ last_digit_limit = max_int64 % base;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (*p == '-') {
+ sign = -1;
+ p++;
+ } else
+ sign = 1;
+
+ l = 0;
+ digit = *p - '0';
+ while (digit >= 0 && digit < base && char_cnt-- > 0) {
+ if (l>limit || (l == limit && digit > last_digit_limit)) {
+ l = max_uint64; /* Truncate on overflow. */
+ break;
+ }
+ l = (l * base) + digit;
+ digit = *++p - '0';
+ }
+ return (sign < 0) ? -l : l;
+}
+
+static int64_t
+ar_atol10(const char *p, unsigned char_cnt)
+{
+ int64_t l, limit, last_digit_limit;
+ int base, digit, sign;
+
+ base = 10;
+ limit = max_int64 / base;
+ last_digit_limit = max_int64 % base;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (*p == '-') {
+ sign = -1;
+ p++;
+ } else
+ sign = 1;
+
+ l = 0;
+ digit = *p - '0';
+ while (digit >= 0 && digit < base && char_cnt-- > 0) {
+ if (l > limit || (l == limit && digit > last_digit_limit)) {
+ l = max_uint64; /* Truncate on overflow. */
+ break;
+ }
+ l = (l * base) + digit;
+ digit = *++p - '0';
+ }
+ return (sign < 0) ? -l : l;
+}
OpenPOWER on IntegriCloud