diff options
author | kientzle <kientzle@FreeBSD.org> | 2004-06-27 01:15:31 +0000 |
---|---|---|
committer | kientzle <kientzle@FreeBSD.org> | 2004-06-27 01:15:31 +0000 |
commit | fc2f6ee069a5f363320dec62ccf54b12ece0d438 (patch) | |
tree | d31c911cb53635ed8be6eeea16ab388e7e4a8941 /lib | |
parent | 4731df16a26e51f2f571e0222cbf4aa767d23583 (diff) | |
download | FreeBSD-src-fc2f6ee069a5f363320dec62ccf54b12ece0d438.zip FreeBSD-src-fc2f6ee069a5f363320dec62ccf54b12ece0d438.tar.gz |
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.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/libarchive/archive.h | 6 | ||||
-rw-r--r-- | lib/libarchive/archive.h.in | 6 | ||||
-rw-r--r-- | lib/libarchive/archive_read.3 | 7 | ||||
-rw-r--r-- | lib/libarchive/archive_read_data_into_buffer.c | 4 | ||||
-rw-r--r-- | lib/libarchive/archive_read_data_into_fd.c | 6 | ||||
-rw-r--r-- | lib/libarchive/archive_read_extract.c | 8 | ||||
-rw-r--r-- | lib/libarchive/archive_read_support_format_tar.c | 181 |
7 files changed, 157 insertions, 61 deletions
diff --git a/lib/libarchive/archive.h b/lib/libarchive/archive.h index c8f5e94..4bef0c9 100644 --- a/lib/libarchive/archive.h +++ b/lib/libarchive/archive.h @@ -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 diff --git a/lib/libarchive/archive.h.in b/lib/libarchive/archive.h.in index c8f5e94..4bef0c9 100644 --- a/lib/libarchive/archive.h.in +++ b/lib/libarchive/archive.h.in @@ -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 diff --git a/lib/libarchive/archive_read.3 b/lib/libarchive/archive_read.3 index f195d57..15910c9 100644 --- a/lib/libarchive/archive_read.3 +++ b/lib/libarchive/archive_read.3 @@ -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. diff --git a/lib/libarchive/archive_read_data_into_buffer.c b/lib/libarchive/archive_read_data_into_buffer.c index 12abb55..0b52617 100644 --- a/lib/libarchive/archive_read_data_into_buffer.c +++ b/lib/libarchive/archive_read_data_into_buffer.c @@ -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); } diff --git a/lib/libarchive/archive_read_data_into_fd.c b/lib/libarchive/archive_read_data_into_fd.c index 85ba98d..b2143ca 100644 --- a/lib/libarchive/archive_read_data_into_fd.c +++ b/lib/libarchive/archive_read_data_into_fd.c @@ -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); } diff --git a/lib/libarchive/archive_read_extract.c b/lib/libarchive/archive_read_extract.c index 8bb75a6..1245d4c 100644 --- a/lib/libarchive/archive_read_extract.c +++ b/lib/libarchive/archive_read_extract.c @@ -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. */ diff --git a/lib/libarchive/archive_read_support_format_tar.c b/lib/libarchive/archive_read_support_format_tar.c index 014ba6c..205f13d 100644 --- a/lib/libarchive/archive_read_support_format_tar.c +++ b/lib/libarchive/archive_read_support_format_tar.c @@ -61,8 +61,51 @@ struct archive_entry_header_ustar { }; /* + * 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; @@ -1132,41 +1194,6 @@ pax_time(const wchar_t *p, int64_t *ps, long *pn) } /* - * 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 */ static int @@ -1212,19 +1239,93 @@ header_gnutar(struct archive *a, struct tar *tar, struct archive_entry *entry, else st->st_rdev = 0; + tar->entry_bytes_remaining = st->st_size; + tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + /* 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)); + if (header->realsize[0] != 0) { + st->st_size = tar_atol(header->realsize, + sizeof(header->realsize)); + } - /* XXX TODO: Recognize and skip extra GNU header blocks. */ - - tar->entry_bytes_remaining = st->st_size; - tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + 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. * |