freebsd-nq/lib/libarchive/archive_read_support_format_ar.c

586 lines
14 KiB
C

/*-
* 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;
}