Read gtar-style sparse archives.
This change also pointed out one API deficiency: the archive_read_data_into_XXX functions were originally defined to return the total bytes read. This is, of course, ambiguous when dealing with non-contiguous files. Change it to just return a status value.
This commit is contained in:
parent
4049589b28
commit
1393f9061e
@ -180,12 +180,12 @@ int archive_read_data_block(struct archive *a,
|
||||
* Some convenience functions that are built on archive_read_data:
|
||||
* 'skip': skips entire entry
|
||||
* 'into_buffer': writes data into memory buffer that you provide
|
||||
* 'into_file': writes data to specified filedes
|
||||
* 'into_fd': writes data to specified filedes
|
||||
*/
|
||||
int archive_read_data_skip(struct archive *);
|
||||
ssize_t archive_read_data_into_buffer(struct archive *, void *buffer,
|
||||
int archive_read_data_into_buffer(struct archive *, void *buffer,
|
||||
ssize_t len);
|
||||
ssize_t archive_read_data_into_fd(struct archive *, int fd);
|
||||
int archive_read_data_into_fd(struct archive *, int fd);
|
||||
|
||||
/*-
|
||||
* Convenience function to recreate the current entry (whose header
|
||||
|
@ -180,12 +180,12 @@ int archive_read_data_block(struct archive *a,
|
||||
* Some convenience functions that are built on archive_read_data:
|
||||
* 'skip': skips entire entry
|
||||
* 'into_buffer': writes data into memory buffer that you provide
|
||||
* 'into_file': writes data to specified filedes
|
||||
* 'into_fd': writes data to specified filedes
|
||||
*/
|
||||
int archive_read_data_skip(struct archive *);
|
||||
ssize_t archive_read_data_into_buffer(struct archive *, void *buffer,
|
||||
int archive_read_data_into_buffer(struct archive *, void *buffer,
|
||||
ssize_t len);
|
||||
ssize_t archive_read_data_into_fd(struct archive *, int fd);
|
||||
int archive_read_data_into_fd(struct archive *, int fd);
|
||||
|
||||
/*-
|
||||
* Convenience function to recreate the current entry (whose header
|
||||
|
@ -168,8 +168,10 @@ a
|
||||
.Tn struct archive_entry .
|
||||
.It Fn archive_read_data
|
||||
Read data associated with the header just read.
|
||||
Internally, this is a convenience function that uses
|
||||
.Fn archive_read_data_block .
|
||||
Internally, this is a convenience function that calls
|
||||
.Fn archive_read_data_block
|
||||
and fills any gaps with nulls so that callers see a single
|
||||
continuous stream of data.
|
||||
.It Fn archive_read_data_block
|
||||
Return the next available block of data for this entry.
|
||||
Unlike
|
||||
@ -393,4 +395,3 @@ The
|
||||
library was written by
|
||||
.An Tim Kientzle Aq kientzle@acm.org .
|
||||
.Sh BUGS
|
||||
The support for GNU tar formats is somewhat limited and should be improved.
|
||||
|
@ -31,7 +31,7 @@ __FBSDID("$FreeBSD$");
|
||||
|
||||
#include "archive.h"
|
||||
|
||||
ssize_t
|
||||
int
|
||||
archive_read_data_into_buffer(struct archive *a, void *d, ssize_t len)
|
||||
{
|
||||
char *dest;
|
||||
@ -45,5 +45,5 @@ archive_read_data_into_buffer(struct archive *a, void *d, ssize_t len)
|
||||
bytes_read = archive_read_data(a, dest + total_bytes,
|
||||
len - total_bytes);
|
||||
}
|
||||
return (total_bytes);
|
||||
return (ARCHIVE_OK);
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ __FBSDID("$FreeBSD$");
|
||||
/*
|
||||
* This implementation minimizes copying of data and is sparse-file aware.
|
||||
*/
|
||||
ssize_t
|
||||
int
|
||||
archive_read_data_into_fd(struct archive *a, int fd)
|
||||
{
|
||||
int r;
|
||||
@ -76,6 +76,6 @@ archive_read_data_into_fd(struct archive *a, int fd)
|
||||
}
|
||||
|
||||
if (r != ARCHIVE_EOF)
|
||||
return (-1);
|
||||
return (total_written);
|
||||
return (r);
|
||||
return (ARCHIVE_OK);
|
||||
}
|
||||
|
@ -319,7 +319,6 @@ archive_read_extract_regular(struct archive *a, struct archive_entry *entry,
|
||||
int flags)
|
||||
{
|
||||
int fd, r;
|
||||
ssize_t s;
|
||||
|
||||
r = ARCHIVE_OK;
|
||||
fd = archive_read_extract_regular_open(a,
|
||||
@ -328,12 +327,7 @@ archive_read_extract_regular(struct archive *a, struct archive_entry *entry,
|
||||
archive_set_error(a, errno, "Can't open");
|
||||
return (ARCHIVE_WARN);
|
||||
}
|
||||
s = archive_read_data_into_fd(a, fd);
|
||||
if (s < archive_entry_size(entry)) {
|
||||
/* Didn't read enough data? Complain but keep going. */
|
||||
archive_set_error(a, EIO, "Archive data truncated");
|
||||
r = ARCHIVE_WARN;
|
||||
}
|
||||
r = archive_read_data_into_fd(a, fd);
|
||||
set_ownership(a, entry, flags);
|
||||
set_time(a, entry, flags);
|
||||
/* Always restore permissions for regular files. */
|
||||
|
@ -60,9 +60,52 @@ struct archive_entry_header_ustar {
|
||||
char prefix[155];
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure of GNU tar header
|
||||
*/
|
||||
struct gnu_sparse {
|
||||
char offset[12];
|
||||
char numbytes[12];
|
||||
};
|
||||
|
||||
struct archive_entry_header_gnutar {
|
||||
char name[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
char size[12];
|
||||
char mtime[12];
|
||||
char checksum[8];
|
||||
char typeflag[1];
|
||||
char linkname[100];
|
||||
char magic[8]; /* "ustar \0" (note blank/blank/null at end) */
|
||||
char uname[32];
|
||||
char gname[32];
|
||||
char devmajor[8];
|
||||
char devminor[8];
|
||||
char atime[12];
|
||||
char ctime[12];
|
||||
char offset[12];
|
||||
char longnames[4];
|
||||
char unused[1];
|
||||
struct gnu_sparse sparse[4];
|
||||
char isextended[1];
|
||||
char realsize[12];
|
||||
/*
|
||||
* GNU doesn't use POSIX 'prefix' field; they use the 'L' (longname)
|
||||
* entry instead.
|
||||
*/
|
||||
};
|
||||
|
||||
/*
|
||||
* Data specific to this format.
|
||||
*/
|
||||
struct sparse_block {
|
||||
struct sparse_block *next;
|
||||
off_t offset;
|
||||
off_t remaining;
|
||||
};
|
||||
|
||||
struct tar {
|
||||
struct archive_string acl_text;
|
||||
struct archive_string entry_name;
|
||||
@ -79,10 +122,15 @@ struct tar {
|
||||
off_t entry_bytes_remaining;
|
||||
off_t entry_offset;
|
||||
off_t entry_padding;
|
||||
struct sparse_block *sparse_list;
|
||||
};
|
||||
|
||||
static size_t UTF8_mbrtowc(wchar_t *pwc, const char *s, size_t n);
|
||||
static int archive_block_is_null(const unsigned char *p);
|
||||
int gnu_read_sparse_data(struct archive *, struct tar *,
|
||||
const struct archive_entry_header_gnutar *header);
|
||||
void gnu_parse_sparse_data(struct archive *, struct tar *,
|
||||
const struct gnu_sparse *sparse, int length);
|
||||
static int header_Solaris_ACL(struct archive *, struct tar *,
|
||||
struct archive_entry *, struct stat *, const void *);
|
||||
static int header_common(struct archive *, struct tar *,
|
||||
@ -295,6 +343,7 @@ archive_read_format_tar_read_data(struct archive *a,
|
||||
{
|
||||
ssize_t bytes_read;
|
||||
struct tar *tar;
|
||||
struct sparse_block *p;
|
||||
|
||||
tar = *(a->pformat_data);
|
||||
if (tar->entry_bytes_remaining > 0) {
|
||||
@ -303,6 +352,19 @@ archive_read_format_tar_read_data(struct archive *a,
|
||||
return (ARCHIVE_FATAL);
|
||||
if (bytes_read > tar->entry_bytes_remaining)
|
||||
bytes_read = tar->entry_bytes_remaining;
|
||||
while (tar->sparse_list != NULL &&
|
||||
tar->sparse_list->remaining == 0) {
|
||||
p = tar->sparse_list;
|
||||
tar->sparse_list = p->next;
|
||||
free(p);
|
||||
if (tar->sparse_list != NULL)
|
||||
tar->entry_offset = tar->sparse_list->offset;
|
||||
}
|
||||
if (tar->sparse_list != NULL) {
|
||||
if (tar->sparse_list->remaining < bytes_read)
|
||||
bytes_read = tar->sparse_list->remaining;
|
||||
tar->sparse_list->remaining -= bytes_read;
|
||||
}
|
||||
*size = bytes_read;
|
||||
*offset = tar->entry_offset;
|
||||
tar->entry_offset += bytes_read;
|
||||
@ -1131,41 +1193,6 @@ pax_time(const wchar_t *p, int64_t *ps, long *pn)
|
||||
} while (l /= 10);
|
||||
}
|
||||
|
||||
/*
|
||||
* Structure of GNU tar header
|
||||
*/
|
||||
struct archive_entry_header_gnutar {
|
||||
char name[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
char size[12];
|
||||
char mtime[12];
|
||||
char checksum[8];
|
||||
char typeflag[1];
|
||||
char linkname[100];
|
||||
char magic[8]; /* "ustar \0" (note blank/blank/null at end) */
|
||||
char uname[32];
|
||||
char gname[32];
|
||||
char devmajor[8];
|
||||
char devminor[8];
|
||||
char atime[12];
|
||||
char ctime[12];
|
||||
char offset[12];
|
||||
char longnames[4];
|
||||
char unused[1];
|
||||
struct {
|
||||
char offset[12];
|
||||
char numbytes[12];
|
||||
} sparse[4];
|
||||
char isextended[1];
|
||||
char realsize[12];
|
||||
/*
|
||||
* GNU doesn't use POSIX 'prefix' field; they use the 'L' (longname)
|
||||
* entry instead.
|
||||
*/
|
||||
};
|
||||
|
||||
/*
|
||||
* Parse GNU tar header
|
||||
*/
|
||||
@ -1212,19 +1239,93 @@ header_gnutar(struct archive *a, struct tar *tar, struct archive_entry *entry,
|
||||
else
|
||||
st->st_rdev = 0;
|
||||
|
||||
/* Grab GNU-specific fields. */
|
||||
/* TODO: FILL THIS IN!!! */
|
||||
st->st_atime = tar_atol(header->atime, sizeof(header->atime));
|
||||
st->st_ctime = tar_atol(header->ctime, sizeof(header->ctime));
|
||||
|
||||
/* XXX TODO: Recognize and skip extra GNU header blocks. */
|
||||
|
||||
tar->entry_bytes_remaining = st->st_size;
|
||||
tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining);
|
||||
|
||||
/* Grab GNU-specific fields. */
|
||||
st->st_atime = tar_atol(header->atime, sizeof(header->atime));
|
||||
st->st_ctime = tar_atol(header->ctime, sizeof(header->ctime));
|
||||
if (header->realsize[0] != 0) {
|
||||
st->st_size = tar_atol(header->realsize,
|
||||
sizeof(header->realsize));
|
||||
}
|
||||
|
||||
if (header->sparse[0].offset[0] != 0) {
|
||||
gnu_read_sparse_data(a, tar, header);
|
||||
} else {
|
||||
if (header->isextended[0] != 0) {
|
||||
/* XXX WTF? XXX */
|
||||
}
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
gnu_read_sparse_data(struct archive *a, struct tar *tar,
|
||||
const struct archive_entry_header_gnutar *header)
|
||||
{
|
||||
ssize_t bytes_read;
|
||||
const void *data;
|
||||
struct extended {
|
||||
struct gnu_sparse sparse[21];
|
||||
char isextended[1];
|
||||
char padding[7];
|
||||
};
|
||||
const struct extended *ext;
|
||||
|
||||
gnu_parse_sparse_data(a, tar, header->sparse, 4);
|
||||
if (header->isextended[0] == 0)
|
||||
return (ARCHIVE_OK);
|
||||
|
||||
do {
|
||||
bytes_read = (a->compression_read_ahead)(a, &data, 512);
|
||||
if (bytes_read < 0)
|
||||
return (ARCHIVE_FATAL);
|
||||
if (bytes_read < 512) {
|
||||
archive_set_error(a, ARCHIVE_ERRNO_FILE_FORMAT,
|
||||
"Truncated tar archive "
|
||||
"detected while reading sparse file data");
|
||||
return (ARCHIVE_FATAL);
|
||||
}
|
||||
(a->compression_read_consume)(a, 512);
|
||||
ext = (const struct extended *)data;
|
||||
gnu_parse_sparse_data(a, tar, ext->sparse, 21);
|
||||
} while (ext->isextended[0] != 0);
|
||||
if (tar->sparse_list != NULL)
|
||||
tar->entry_offset = tar->sparse_list->offset;
|
||||
return (ARCHIVE_OK);
|
||||
}
|
||||
|
||||
void
|
||||
gnu_parse_sparse_data(struct archive *a, struct tar *tar,
|
||||
const struct gnu_sparse *sparse, int length)
|
||||
{
|
||||
struct sparse_block *last;
|
||||
struct sparse_block *p;
|
||||
|
||||
(void)a; /* UNUSED */
|
||||
|
||||
last = tar->sparse_list;
|
||||
while (last != NULL && last->next != NULL)
|
||||
last = last->next;
|
||||
|
||||
while (length > 0 && sparse->offset[0] != 0) {
|
||||
p = malloc(sizeof(*p));
|
||||
memset(p, 0, sizeof(*p));
|
||||
if (last != NULL)
|
||||
last->next = p;
|
||||
else
|
||||
tar->sparse_list = p;
|
||||
last = p;
|
||||
p->offset = tar_atol(sparse->offset, sizeof(sparse->offset));
|
||||
p->remaining =
|
||||
tar_atol(sparse->numbytes, sizeof(sparse->numbytes));
|
||||
sparse++;
|
||||
length--;
|
||||
}
|
||||
}
|
||||
|
||||
/*-
|
||||
* Convert text->integer.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user