'ar' format support for libarchive, contributed by Kai Wang.

This commit is contained in:
Tim Kientzle 2007-04-03 05:34:36 +00:00
parent 02b71ede34
commit dedbe8ed70
6 changed files with 1376 additions and 0 deletions

View File

@ -177,6 +177,9 @@ typedef int archive_close_callback(struct archive *, void *_client_data);
#define ARCHIVE_FORMAT_ISO9660_ROCKRIDGE (ARCHIVE_FORMAT_ISO9660 | 1)
#define ARCHIVE_FORMAT_ZIP 0x50000
#define ARCHIVE_FORMAT_EMPTY 0x60000
#define ARCHIVE_FORMAT_AR 0x70000
#define ARCHIVE_FORMAT_AR_SVR4 (ARCHIVE_FORMAT_AR | 1)
#define ARCHIVE_FORMAT_AR_BSD (ARCHIVE_FORMAT_AR | 2)
/*-
* Basic outline for reading an archive:
@ -206,6 +209,7 @@ int archive_read_support_compression_gzip(struct archive *);
int archive_read_support_compression_none(struct archive *);
int archive_read_support_format_all(struct archive *);
int archive_read_support_format_ar(struct archive *);
int archive_read_support_format_cpio(struct archive *);
int archive_read_support_format_empty(struct archive *);
int archive_read_support_format_gnutar(struct archive *);
@ -374,6 +378,8 @@ int archive_write_set_format(struct archive *, int format_code);
int archive_write_set_format_by_name(struct archive *,
const char *name);
/* To minimize link pollution, use one or more of the following. */
int archive_write_set_format_ar_bsd(struct archive *);
int archive_write_set_format_ar_svr4(struct archive *);
int archive_write_set_format_cpio(struct archive *);
/* TODO: int archive_write_set_format_old_tar(struct archive *); */
int archive_write_set_format_pax(struct archive *);

View File

@ -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;
}

View File

@ -0,0 +1,492 @@
/*-
* 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_SYS_STAT_H
#include <sys/stat.h>
#endif
#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_write_private.h"
struct ar_w {
uint64_t entry_bytes_remaining;
uint64_t entry_padding;
int is_strtab;
int has_strtab;
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); */
static int __archive_write_set_format_ar(struct archive_write *);
static int archive_write_ar_header(struct archive_write *,
struct archive_entry *);
static ssize_t archive_write_ar_data(struct archive_write *, const void *buff,
size_t s);
static int archive_write_ar_destroy(struct archive_write *);
static int archive_write_ar_finish_entry(struct archive_write *);
static int format_octal(int64_t v, char *p, int s);
static int format_decimal(int64_t v, char *p, int s);
int
archive_write_set_format_ar_bsd(struct archive *_a)
{
struct archive_write *a = (struct archive_write *)_a;
int r = __archive_write_set_format_ar(a);
if (r == ARCHIVE_OK) {
a->archive_format = ARCHIVE_FORMAT_AR_BSD;
a->archive_format_name = "ar (BSD)";
}
return (r);
}
int
archive_write_set_format_ar_svr4(struct archive *_a)
{
struct archive_write *a = (struct archive_write *)_a;
int r = __archive_write_set_format_ar(a);
if (r == ARCHIVE_OK) {
a->archive_format = ARCHIVE_FORMAT_AR_SVR4;
a->archive_format_name = "ar (GNU/SVR4)";
}
return (r);
}
/*
* Generic initialization.
*/
static int
__archive_write_set_format_ar(struct archive_write *a)
{
struct ar_w *ar;
/* If someone else was already registered, unregister them. */
if (a->format_destroy != NULL)
(a->format_destroy)(a);
ar = (struct ar_w *)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));
a->format_data = ar;
a->format_write_header = archive_write_ar_header;
a->format_write_data = archive_write_ar_data;
a->format_finish = NULL;
a->format_destroy = archive_write_ar_destroy;
a->format_finish_entry = archive_write_ar_finish_entry;
return (ARCHIVE_OK);
}
static int
archive_write_ar_header(struct archive_write *a, struct archive_entry *entry)
{
int ret, append_fn;
char buff[60];
char *ss, *se;
struct ar_w *ar;
const char *pp;
const struct stat *st;
ret = 0;
append_fn = 0;
ar = (struct ar_w *)a->format_data;
ar->is_strtab = 0;
if (a->archive.file_position == 0) {
/*
* We are now at the beginning of the archive,
* so we need first write the ar global header.
*/
(a->compression_write)(a, ARMAG, SARMAG);
}
memset(buff, ' ', 60);
strncpy(&buff[AR_fmag_offset], ARFMAG, SARFMAG);
pp = archive_entry_pathname(entry);
if (strcmp(pp, "/") == 0 ) {
/* Entry is archive symbol table in GNU format */
buff[AR_name_offset] = '/';
goto stat;
}
if (strcmp(pp, "__.SYMDEF") == 0) {
/* Entry is archive symbol table in BSD format */
strncpy(buff + AR_name_offset, "__.SYMDEF", 9);
goto stat;
}
if (strcmp(pp, "//") == 0) {
/*
* Entry is archive string table, inform that we should
* collect strtab in next _data call.
*/
ar->is_strtab = 1;
buff[AR_name_offset] = buff[AR_name_offset + 1] = '/';
/*
* For archive string table, only ar_size filed should
* be set.
*/
goto size;
}
/* Otherwise, entry is a normal archive member. */
if (a->archive_format == ARCHIVE_FORMAT_AR_SVR4) {
/*
* SVR4/GNU variant use a "/" to mark then end of the filename,
* make it possible to have embedded spaces in the filename.
* So, the longest filename here (without extension) is
* actually 15 bytes.
*/
if (strlen(pp) <= 15) {
strncpy(&buff[AR_name_offset], pp, strlen(pp));
buff[AR_name_offset + strlen(pp)] = '/';
} else {
/*
* For filename longer than 15 bytes, GNU variant
* makes use of a string table and instead stores the
* offset of the real filename to in the ar_name field.
* The string table should have been written before.
*/
if (ar->has_strtab <= 0) {
archive_set_error(&a->archive, EINVAL,
"Can't find string table");
return (ARCHIVE_WARN);
}
se = (char *)malloc(strlen(pp) + 3);
if (se == NULL) {
archive_set_error(&a->archive, ENOMEM,
"Can't allocate filename buffer");
return (ARCHIVE_FATAL);
}
strncpy(se, pp, strlen(pp));
strcpy(se + strlen(pp), "/\n");
ss = strstr(ar->strtab, se);
free(se);
if (ss == NULL) {
archive_set_error(&a->archive, EINVAL,
"Invalid string table");
return (ARCHIVE_WARN);
}
/*
* GNU variant puts "/" followed by digits into
* ar_name field. These digits indicates the real
* filename string's offset to the string table.
*/
buff[AR_name_offset] = '/';
if (format_decimal(ss - ar->strtab,
buff + AR_name_offset + 1,
AR_name_size - 1)) {
archive_set_error(&a->archive, ERANGE,
"string table offset too large");
return (ARCHIVE_WARN);
}
}
} else if (a->archive_format == ARCHIVE_FORMAT_AR_BSD) {
/*
* BSD variant: for any file name which is more than
* 16 chars or contains one or more embedded space(s), the
* string "#1/" followed by the ASCII length of the name is
* put into the ar_name field. The file size (stored in the
* ar_size field) is incremented by the length of the name.
* The name is then written immediately following the
* archive header.
*/
if (strlen(pp) <= 16 && strchr(pp, ' ') == NULL) {
strncpy(&buff[AR_name_offset], pp, strlen(pp));
buff[AR_name_offset + strlen(pp)] = ' ';
}
else {
strncpy(buff + AR_name_offset, AR_EFMT1, SAR_EFMT1);
if (format_decimal(strlen(pp),
buff + AR_name_offset + SAR_EFMT1,
AR_name_size - SAR_EFMT1)) {
archive_set_error(&a->archive, ERANGE,
"File name too long");
return (ARCHIVE_WARN);
}
append_fn = 1;
archive_entry_set_size(entry,
archive_entry_size(entry) + strlen(pp));
}
}
stat:
st = archive_entry_stat(entry);
if (format_decimal(st->st_mtime, buff + AR_date_offset, AR_date_size)) {
archive_set_error(&a->archive, ERANGE,
"File modification time too large");
return (ARCHIVE_WARN);
}
if (format_decimal(st->st_uid, buff + AR_uid_offset, AR_uid_size)) {
archive_set_error(&a->archive, ERANGE,
"Numeric user ID too large");
return (ARCHIVE_WARN);
}
if (format_decimal(st->st_gid, buff + AR_gid_offset, AR_gid_size)) {
archive_set_error(&a->archive, ERANGE,
"Numeric group ID too large");
return (ARCHIVE_WARN);
}
if (format_octal(st->st_mode, buff + AR_mode_offset, AR_mode_size)) {
archive_set_error(&a->archive, ERANGE,
"Numeric mode too large");
return (ARCHIVE_WARN);
}
size:
if (format_decimal(archive_entry_size(entry), buff + AR_size_offset,
AR_size_size)) {
archive_set_error(&a->archive, ERANGE,
"File size out of range");
return (ARCHIVE_WARN);
}
ret = (a->compression_write)(a, buff, 60);
if (ret != ARCHIVE_OK)
return (ret);
ar->entry_bytes_remaining = archive_entry_size(entry);
ar->entry_padding = ar->entry_bytes_remaining % 2;
if (append_fn > 0) {
ret = (a->compression_write)(a, pp, strlen(pp));
if (ret != ARCHIVE_OK)
return (ret);
ar->entry_bytes_remaining -= strlen(pp);
}
return (ARCHIVE_OK);
}
static ssize_t
archive_write_ar_data(struct archive_write *a, const void *buff, size_t s)
{
struct ar_w *ar;
int ret;
ar = (struct ar_w *)a->format_data;
if (s > ar->entry_bytes_remaining)
s = ar->entry_bytes_remaining;
if (ar->is_strtab > 0) {
if (ar->has_strtab > 0) {
archive_set_error(&a->archive, EINVAL,
"More than one string tables exist");
return (ARCHIVE_WARN);
}
ar->strtab = (char *)malloc(s);
if (ar->strtab == NULL) {
archive_set_error(&a->archive, ENOMEM,
"Can't allocate strtab buffer");
return (ARCHIVE_FATAL);
}
strncpy(ar->strtab, buff, s);
ar->has_strtab = 1;
}
ret = (a->compression_write)(a, buff, s);
if (ret != ARCHIVE_OK)
return (ret);
ar->entry_bytes_remaining -= s;
return (s);
}
static int
archive_write_ar_destroy(struct archive_write *a)
{
struct ar_w *ar;
ar = (struct ar_w *)a->format_data;
if (ar->has_strtab > 0) {
free(ar->strtab);
ar->strtab = NULL;
}
free(ar);
a->format_data = NULL;
return (ARCHIVE_OK);
}
static int
archive_write_ar_finish_entry(struct archive_write *a)
{
struct ar_w *ar;
int ret;
ar = (struct ar_w *)a->format_data;
if (ar->entry_bytes_remaining != 0) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
"Entry remaining bytes larger than 0");
return (ARCHIVE_WARN);
}
if (ar->entry_padding == 0) {
return (ARCHIVE_OK);
}
if (ar->entry_padding != 1) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
"Padding wrong size: %d should be 1 or 0",
ar->entry_padding);
return (ARCHIVE_WARN);
}
ret = (a->compression_write)(a, "\n", 1);
return (ret);
}
/*
* Format a number into the specified field using base-8.
* NB: This version is slightly different from the one in
* _ustar.c
*/
static int
format_octal(int64_t v, char *p, int s)
{
int len;
char *h;
len = s;
h = p;
/* 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. */
do {
*--p = (char)('0' + (v & 7));
v >>= 3;
} while (--s > 0 && v > 0);
if (v == 0) {
memmove(h, p, len - s);
p = h + len - s;
while (s-- > 0)
*p++ = ' ';
return (0);
}
/* If it overflowed, fill field with max value. */
while (len-- > 0)
*p++ = '7';
return (-1);
}
/*
* Format a number into the specified field using base-10.
*/
static int
format_decimal(int64_t v, char *p, int s)
{
int len;
char *h;
len = s;
h = p;
/* Negative values in ar header are meaningless , so use 0. */
if (v < 0) {
while (len-- > 0)
*p++ = '0';
return (-1);
}
p += s;
do {
*--p = (char)('0' + (v % 10));
v /= 10;
} while (--s > 0 && v > 0);
if (v == 0) {
memmove(h, p, len - s);
p = h + len - s;
while (s-- > 0)
*p++ = ' ';
return (0);
}
/* If it overflowed, fill field with max value. */
while (len-- > 0)
*p++ = '9';
return (-1);
}

View File

@ -235,7 +235,23 @@ compressed with the
.Dq deflate
algorithm.
Older zip compression algorithms are not supported.
.Ss Archive (library) file format
The Unix archives (commonly created by the
.Xr ar 1
archiver) are mainly used as libraries of object files intended to
be loaded using the link-editor
.Xr ld 1 .
Altough it is capable of creating archives for any purpose,
nowadays it is used only to create static libraries and
for other purpose,
.Xr tar 1
is the prefered way. The ar format has never been standardised,
currently there exist two commonly used format, one is
the GNU variant derived from SVR4, the other is the
BSD format first appeared in 4.4BSD.
Libarchive provides read and write support for both two variants.
.Sh SEE ALSO
.Xr ar 1 ,
.Xr cpio 1 ,
.Xr mkisofs 1 ,
.Xr shar 1 ,

View File

@ -0,0 +1,104 @@
/*-
* 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 "test.h"
__FBSDID("$FreeBSD$");
/*
* This "archive" is created by "GNU ar". Here we try to verify
* our GNU format handling functionality.
*/
static unsigned char archive[] = {
'!','<','a','r','c','h','>',10,'/','/',' ',' ',' ',' ',' ',' ',' ',
' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
' ',' ',' ',' ',' ','4','0',' ',' ',' ',' ',' ',' ',' ',' ','`',10,
'y','y','y','t','t','t','s','s','s','a','a','a','f','f','f','.','o',
'/',10,'h','h','h','h','j','j','j','j','k','k','k','k','l','l','l',
'l','.','o','/',10,10,'/','0',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
' ',' ',' ',' ','1','1','7','5','4','6','5','6','5','2',' ',' ','1',
'0','0','1',' ',' ','0',' ',' ',' ',' ',' ','1','0','0','6','4','4',
' ',' ','8',' ',' ',' ',' ',' ',' ',' ',' ',' ','`',10,'5','5','6',
'6','7','7','8','8','g','g','h','h','.','o','/',' ',' ',' ',' ',' ',
' ',' ',' ',' ','1','1','7','5','4','6','5','6','6','8',' ',' ','1',
'0','0','1',' ',' ','0',' ',' ',' ',' ',' ','1','0','0','6','4','4',
' ',' ','4',' ',' ',' ',' ',' ',' ',' ',' ',' ','`',10,'3','3','3',
'3','/','1','9',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
'1','1','7','5','4','6','5','7','1','3',' ',' ','1','0','0','1',' ',
' ','0',' ',' ',' ',' ',' ','1','0','0','6','4','4',' ',' ','9',' ',
' ',' ',' ',' ',' ',' ',' ',' ','`',10,'9','8','7','6','5','4','3',
'2','1',10};
char buff[64];
DEFINE_TEST(test_read_format_ar)
{
struct archive_entry *ae;
struct archive *a;
assert((a = archive_read_new()) != NULL);
assertA(0 == archive_read_support_compression_all(a));
assertA(0 == archive_read_support_format_all(a));
assertA(0 == archive_read_open_memory(a, archive, sizeof(archive)));
/* First we meet the string table */
assertA(0 == archive_read_next_header(a, &ae));
assertA(archive_compression(a) == ARCHIVE_COMPRESSION_NONE);
assertA(archive_format(a) == ARCHIVE_FORMAT_AR);
assert(0 == strcmp("//", archive_entry_pathname(ae)));
assert(40 == archive_entry_size(ae));
assertA(40 == archive_read_data(a, buff, 45));
/* First Entry */
assertA(0 == archive_read_next_header(a, &ae));
assert(0 == strcmp("yyytttsssaaafff.o", archive_entry_pathname(ae)));
assert(8 == archive_entry_size(ae));
assertA(8 == archive_read_data(a, buff, 10));
assert(0 == memcmp(buff, "55667788", 8));
/* Second Entry */
assertA(0 == archive_read_next_header(a, &ae));
assert(0 == strcmp("gghh.o", archive_entry_pathname(ae)));
assert(4 == archive_entry_size(ae));
assertA(4 == archive_read_data(a, buff, 10));
assert(0 == memcmp(buff, "3333", 4));
/* Third Entry */
assertA(0 == archive_read_next_header(a, &ae));
assert(0 == strcmp("hhhhjjjjkkkkllll.o", archive_entry_pathname(ae)));
assert(9 == archive_entry_size(ae));
assertA(9 == archive_read_data(a, buff, 9));
assert(0 == memcmp(buff, "987654321", 9));
/* Test EOF */
assertA(1 == archive_read_next_header(a, &ae));
assert(0 == archive_read_close(a));
#if ARCHIVE_API_VERSION > 1
assert(0 == archive_read_finish(a));
#else
archive_read_finish(a);
#endif
}

View File

@ -0,0 +1,173 @@
/*-
* 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 "test.h"
__FBSDID("$FreeBSD$");
char buff[4096];
char buff2[64];
static unsigned char strtab[] = "abcdefghijklmn.o/\nggghhhjjjrrrttt.o/\n\n";
DEFINE_TEST(test_write_format_ar)
{
struct archive_entry *ae;
struct archive* a;
size_t used;
/*
* First we try to create a SVR4/GNU format archive.
*/
assert((a = archive_write_new()) != NULL);
assertA(0 == archive_write_set_format_ar_svr4(a));
assertA(0 == archive_write_set_compression_none(a));
assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used));
/* write the string table */
assert((ae = archive_entry_new()) != NULL);
archive_entry_copy_pathname(ae, "//");
archive_entry_set_size(ae, strlen(strtab));
assertA(0 == archive_write_header(a, ae));
assertA(strlen(strtab) == archive_write_data(a, strtab, strlen(strtab)));
archive_entry_free(ae);
/* write entries */
assert((ae = archive_entry_new()) != NULL);
archive_entry_set_mtime(ae, 1, 0);
assert(1 == archive_entry_mtime(ae));
archive_entry_set_mode(ae, S_IFREG | 0755);
assert((S_IFREG | 0755) == archive_entry_mode(ae));
archive_entry_copy_pathname(ae, "abcdefghijklmn.o");
archive_entry_set_size(ae, 8);
assertA(0 == archive_write_header(a, ae));
assertA(8 == archive_write_data(a, "87654321", 15));
archive_entry_free(ae);
assert((ae = archive_entry_new()) != NULL);
archive_entry_copy_pathname(ae, "ggghhhjjjrrrttt.o");
archive_entry_set_size(ae, 7);
assertA(0 == archive_write_header(a, ae));
assertA(7 == archive_write_data(a, "7777777", 7));
archive_entry_free(ae);
archive_write_close(a);
#if ARCHIVE_API_VERSION > 1
assert(0 == archive_write_finish(a));
#elif
archive_write_finish(a);
#endif
/*
* Now, read the data back.
*/
assert((a = archive_read_new()) != NULL);
assertA(0 == archive_read_support_format_all(a));
assertA(0 == archive_read_support_compression_all(a));
assertA(0 == archive_read_open_memory(a, buff, used));
assertA(0 == archive_read_next_header(a, &ae));
assert(0 == strcmp("//", archive_entry_pathname(ae)));
assert(strlen(strtab) == archive_entry_size(ae));
assertA(strlen(strtab) == archive_read_data(a, buff2, strlen(strtab)));
assert(0 == memcmp(buff2, strtab, strlen(strtab)));
assertA(0 == archive_read_next_header(a, &ae));
assert(1 == archive_entry_mtime(ae));
assert(0 == strcmp("abcdefghijklmn.o", archive_entry_pathname(ae)));
assert(8 == archive_entry_size(ae));
assertA(8 == archive_read_data(a, buff2, 10));
assert(0 == memcmp(buff2, "87654321", 8));
assert(0 == archive_read_next_header(a, &ae));
assert(0 == strcmp("ggghhhjjjrrrttt.o", archive_entry_pathname(ae)));
assert(7 == archive_entry_size(ae));
assertA(7 == archive_read_data(a, buff2, 11));
assert(0 == memcmp(buff2, "7777777", 7));
assert(0 == archive_read_close(a));
#if ARCHIVE_API_VERSION > 1
assert(0 == archive_read_finish(a));
#else
archive_read_finish(a);
#endif
/*
* Then, we try to create a BSD format archive.
*/
assert((a = archive_write_new()) != NULL);
assertA(0 == archive_write_set_format_ar_bsd(a));
assertA(0 == archive_write_set_compression_none(a));
assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used));
/* write a entry need long name extension */
assert((ae = archive_entry_new()) != NULL);
archive_entry_copy_pathname(ae, "ttttyyyyuuuuiiii.o");
archive_entry_set_size(ae, 5);
assertA(0 == archive_write_header(a, ae));
assertA(5 == archive_write_data(a, "12345", 7));
archive_entry_free(ae);
/* write a entry with a short name */
assert((ae = archive_entry_new()) != NULL);
archive_entry_copy_pathname(ae, "ttyy.o");
archive_entry_set_size(ae, 6);
assertA(0 == archive_write_header(a, ae));
assertA(6 == archive_write_data(a, "555555", 7));
archive_entry_free(ae);
archive_write_close(a);
#if ARCHIVE_API_VERSION > 1
assert(0 == archive_write_finish(a));
#elif
archive_write_finish(a);
#endif
/* Now, Read the data back */
assert((a = archive_read_new()) != NULL);
assertA(0 == archive_read_support_format_all(a));
assertA(0 == archive_read_support_compression_all(a));
assertA(0 == archive_read_open_memory(a, buff, used));
assert(0 == archive_read_next_header(a, &ae));
assert(0 == strcmp("ttttyyyyuuuuiiii.o", archive_entry_pathname(ae)));
assert(5 == archive_entry_size(ae));
assertA(5 == archive_read_data(a, buff2, 10));
assert(0 == memcmp(buff2, "12345", 5));
assert(0 == archive_read_next_header(a, &ae));
assert(0 == strcmp("ttyy.o", archive_entry_pathname(ae)));
assert(6 == archive_entry_size(ae));
assertA(6 == archive_read_data(a, buff2, 10));
assert(0 == memcmp(buff2, "555555", 6));
/* Test EOF */
assertA(1 == archive_read_next_header(a, &ae));
assert(0 == archive_read_close(a));
#if ARCHIVE_API_VERSION > 1
assert(0 == archive_read_finish(a));
#else
archive_read_finish(a);
#endif
}