summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorkientzle <kientzle@FreeBSD.org>2008-09-05 06:13:11 +0000
committerkientzle <kientzle@FreeBSD.org>2008-09-05 06:13:11 +0000
commit5290cd20ad8870351f499170163f9f1d27cf4c4a (patch)
treef26bca05c3fe785f6ab102b024f772ec20aba333 /lib
parent88514167fd6e746fb46c7475739be2223186c9ba (diff)
downloadFreeBSD-src-5290cd20ad8870351f499170163f9f1d27cf4c4a.zip
FreeBSD-src-5290cd20ad8870351f499170163f9f1d27cf4c4a.tar.gz
MfP4: Handle entries with unset size properly: Regular files with
unspecified size are "unlimited" (required by Zip reader, which sometimes does not know the uncompressed size of an entry until it gets to the end). Also, hardlinks with unspecified (or zero) size do not overwrite the data on disk nor do they set metadata. This is compatible with GNU tar and NetBSD pax behavior.
Diffstat (limited to 'lib')
-rw-r--r--lib/libarchive/archive_write_disk.c124
-rw-r--r--lib/libarchive/test/test_write_disk.c71
-rw-r--r--lib/libarchive/test/test_write_disk_hardlink.c87
3 files changed, 223 insertions, 59 deletions
diff --git a/lib/libarchive/archive_write_disk.c b/lib/libarchive/archive_write_disk.c
index 14a0529..b7313bc 100644
--- a/lib/libarchive/archive_write_disk.c
+++ b/lib/libarchive/archive_write_disk.c
@@ -176,7 +176,7 @@ struct archive_write_disk {
int fd;
/* Current offset for writing data to the file. */
off_t offset;
- /* Maximum size of file. */
+ /* Maximum size of file, -1 if unknown. */
off_t filesize;
/* Dir we were in before this restore; only for deep paths. */
int restore_pwd;
@@ -231,7 +231,8 @@ static int set_time(struct archive_write_disk *);
static struct fixup_entry *sort_dir_list(struct fixup_entry *p);
static gid_t trivial_lookup_gid(void *, const char *, gid_t);
static uid_t trivial_lookup_uid(void *, const char *, uid_t);
-
+static ssize_t write_data_block(struct archive_write_disk *,
+ const char *, size_t, off_t);
static struct archive_vtable *archive_write_disk_vtable(void);
@@ -337,7 +338,10 @@ _archive_write_header(struct archive *_a, struct archive_entry *entry)
a->offset = 0;
a->uid = a->user_uid;
a->mode = archive_entry_mode(a->entry);
- a->filesize = archive_entry_size(a->entry);
+ if (archive_entry_size_is_set(a->entry))
+ a->filesize = archive_entry_size(a->entry);
+ else
+ a->filesize = -1;
archive_strcpy(&(a->_name_data), archive_entry_pathname(a->entry));
a->name = a->_name_data.s;
archive_clear_error(&a->archive);
@@ -496,89 +500,113 @@ archive_write_disk_set_skip_file(struct archive *_a, dev_t d, ino_t i)
}
static ssize_t
-_archive_write_data_block(struct archive *_a,
- const void *buff, size_t size, off_t offset)
+write_data_block(struct archive_write_disk *a,
+ const char *buff, size_t size, off_t offset)
{
- struct archive_write_disk *a = (struct archive_write_disk *)_a;
ssize_t bytes_written = 0;
- ssize_t block_size, bytes_to_write;
- int r = ARCHIVE_OK;
+ ssize_t block_size = 0, bytes_to_write;
+ int r;
- __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
- ARCHIVE_STATE_DATA, "archive_write_disk_block");
- if (a->fd < 0) {
- archive_set_error(&a->archive, 0, "File not open");
+ if (a->filesize == 0 || a->fd < 0) {
+ archive_set_error(&a->archive, 0,
+ "Attempt to write to an empty file");
return (ARCHIVE_WARN);
}
- archive_clear_error(&a->archive);
if (a->flags & ARCHIVE_EXTRACT_SPARSE) {
if ((r = _archive_write_disk_lazy_stat(a)) != ARCHIVE_OK)
return (r);
block_size = a->pst->st_blksize;
- } else
- block_size = -1;
-
- if ((off_t)(offset + size) > a->filesize) {
- size = (size_t)(a->filesize - a->offset);
- archive_set_error(&a->archive, 0,
- "Write request too large");
- r = ARCHIVE_WARN;
}
+ if (a->filesize >= 0 && (off_t)(offset + size) > a->filesize)
+ size = (size_t)(a->filesize - offset);
+
/* Write the data. */
while (size > 0) {
- if (block_size != -1) {
- const char *buf;
+ if (block_size == 0) {
+ bytes_to_write = size;
+ } else {
+ /* We're sparsifying the file. */
+ const char *p, *end;
+ off_t block_end;
- for (buf = buff; size; ++buf, --size, ++offset) {
- if (*buf != '\0')
+ /* Skip leading zero bytes. */
+ for (p = buff, end = buff + size; p < end; ++p) {
+ if (*p != '\0')
break;
}
+ offset += p - buff;
+ size -= p - buff;
+ buff = p;
if (size == 0)
break;
- bytes_to_write = block_size - offset % block_size;
- buff = buf;
- } else
+
+ /* Calculate next block boundary after offset. */
+ block_end
+ = (offset / block_size) * block_size + block_size;
+
+ /* If the adjusted write would cross block boundary,
+ * truncate it to the block boundary. */
bytes_to_write = size;
+ if (offset + bytes_to_write > block_end)
+ bytes_to_write = block_end - offset;
+ }
+
/* Seek if necessary to the specified offset. */
if (offset != a->last_offset) {
if (lseek(a->fd, offset, SEEK_SET) < 0) {
- archive_set_error(&a->archive, errno, "Seek failed");
+ archive_set_error(&a->archive, errno,
+ "Seek failed");
return (ARCHIVE_FATAL);
}
}
- bytes_written = write(a->fd, buff, size);
+ bytes_written = write(a->fd, buff, bytes_to_write);
if (bytes_written < 0) {
archive_set_error(&a->archive, errno, "Write failed");
return (ARCHIVE_WARN);
}
- buff = (const char *)buff + bytes_written;
+ buff += bytes_written;
size -= bytes_written;
offset += bytes_written;
a->archive.file_position += bytes_written;
a->archive.raw_position += bytes_written;
a->last_offset = a->offset = offset;
}
- a->offset = offset;
- return (r);
+ return (bytes_written);
+}
+
+static ssize_t
+_archive_write_data_block(struct archive *_a,
+ const void *buff, size_t size, off_t offset)
+{
+ struct archive_write_disk *a = (struct archive_write_disk *)_a;
+ ssize_t r;
+
+ __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
+ ARCHIVE_STATE_DATA, "archive_write_disk_block");
+
+ r = write_data_block(a, buff, size, offset);
+
+ if (r < 0)
+ return (r);
+ if ((size_t)r < size) {
+ archive_set_error(&a->archive, 0,
+ "Write request too large");
+ return (ARCHIVE_WARN);
+ }
+ return (ARCHIVE_OK);
}
static ssize_t
_archive_write_data(struct archive *_a, const void *buff, size_t size)
{
struct archive_write_disk *a = (struct archive_write_disk *)_a;
- int r;
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
ARCHIVE_STATE_DATA, "archive_write_data");
- if (a->fd < 0)
- return (ARCHIVE_OK);
- r = _archive_write_data_block(_a, buff, size, a->offset);
- if (r < ARCHIVE_OK)
- return (r);
- return size;
+ return (write_data_block(a, buff, size, a->offset));
}
static int
@@ -594,7 +622,15 @@ _archive_write_finish_entry(struct archive *_a)
return (ARCHIVE_OK);
archive_clear_error(&a->archive);
- if (a->last_offset != a->filesize && a->fd >= 0) {
+ /* Pad or truncate file to the right size. */
+ if (a->fd < 0) {
+ /* There's no file. */
+ } else if (a->filesize < 0) {
+ /* File size is unknown, so we can't set the size. */
+ } else if (a->last_offset == a->filesize) {
+ /* Last write ended at exactly the filesize; we're done. */
+ /* Hopefully, this is the common case. */
+ } else {
if (ftruncate(a->fd, a->filesize) == -1 &&
a->filesize == 0) {
archive_set_error(&a->archive, errno,
@@ -611,7 +647,8 @@ _archive_write_finish_entry(struct archive *_a)
if (a->st.st_size != a->filesize) {
const char nul = '\0';
if (lseek(a->fd, a->st.st_size - 1, SEEK_SET) < 0) {
- archive_set_error(&a->archive, errno, "Seek failed");
+ archive_set_error(&a->archive, errno,
+ "Seek failed");
return (ARCHIVE_FATAL);
}
if (write(a->fd, &nul, 1) < 0) {
@@ -619,6 +656,7 @@ _archive_write_finish_entry(struct archive *_a)
"Write to restore size failed");
return (ARCHIVE_FATAL);
}
+ a->pst = NULL;
}
}
@@ -973,7 +1011,7 @@ create_filesystem_object(struct archive_write_disk *a)
* If the hardlink does carry data, let the last
* archive entry decide ownership.
*/
- if (r == 0 && a->filesize == 0) {
+ if (r == 0 && a->filesize <= 0) {
a->todo = 0;
a->deferred = 0;
} if (r == 0 && a->filesize > 0) {
diff --git a/lib/libarchive/test/test_write_disk.c b/lib/libarchive/test/test_write_disk.c
index 41eb1d8..b91288b 100644
--- a/lib/libarchive/test/test_write_disk.c
+++ b/lib/libarchive/test/test_write_disk.c
@@ -84,7 +84,7 @@ static void create_reg_file(struct archive_entry *ae, const char *msg)
* the entry being a maximum size.
*/
archive_entry_set_size(ae, sizeof(data));
- archive_entry_set_mtime(ae, 123456789, 0);
+ archive_entry_set_mtime(ae, 123456789, 0);
assertEqualIntA(ad, 0, archive_write_header(ad, ae));
assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data)));
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
@@ -152,6 +152,61 @@ static void create_reg_file2(struct archive_entry *ae, const char *msg)
free(compare);
free(data);
}
+
+static void create_reg_file3(struct archive_entry *ae, const char *msg)
+{
+ static const char data[]="abcdefghijklmnopqrstuvwxyz";
+ struct archive *ad;
+ struct stat st;
+
+ /* Write the entry to disk. */
+ assert((ad = archive_write_disk_new()) != NULL);
+ failure("%s", msg);
+ /* Set the size smaller than the data and verify the truncation. */
+ archive_entry_set_size(ae, 5);
+ assertEqualIntA(ad, 0, archive_write_header(ad, ae));
+ assertEqualInt(5, archive_write_data(ad, data, sizeof(data)));
+ assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
+#if ARCHIVE_VERSION_NUMBER < 2000000
+ archive_write_finish(ad);
+#else
+ assertEqualInt(0, archive_write_finish(ad));
+#endif
+ /* Test the entry on disk. */
+ assert(0 == stat(archive_entry_pathname(ae), &st));
+ failure("st.st_mode=%o archive_entry_mode(ae)=%o",
+ st.st_mode, archive_entry_mode(ae));
+ assertEqualInt(st.st_mode, (archive_entry_mode(ae) & ~UMASK));
+ assertEqualInt(st.st_size, 5);
+}
+
+
+static void create_reg_file4(struct archive_entry *ae, const char *msg)
+{
+ static const char data[]="abcdefghijklmnopqrstuvwxyz";
+ struct archive *ad;
+ struct stat st;
+
+ /* Write the entry to disk. */
+ assert((ad = archive_write_disk_new()) != NULL);
+ /* Leave the size unset. The data should not be truncated. */
+ assertEqualIntA(ad, 0, archive_write_header(ad, ae));
+ assertEqualInt(ARCHIVE_OK,
+ archive_write_data_block(ad, data, sizeof(data), 0));
+ assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
+#if ARCHIVE_VERSION_NUMBER < 2000000
+ archive_write_finish(ad);
+#else
+ assertEqualInt(0, archive_write_finish(ad));
+#endif
+ /* Test the entry on disk. */
+ assert(0 == stat(archive_entry_pathname(ae), &st));
+ failure("st.st_mode=%o archive_entry_mode(ae)=%o",
+ st.st_mode, archive_entry_mode(ae));
+ assertEqualInt(st.st_mode, (archive_entry_mode(ae) & ~UMASK));
+ failure(msg);
+ assertEqualInt(st.st_size, sizeof(data));
+}
#endif
DEFINE_TEST(test_write_disk)
@@ -178,6 +233,20 @@ DEFINE_TEST(test_write_disk)
create_reg_file2(ae, "Test creating another regular file");
archive_entry_free(ae);
+ /* A regular file with a size restriction */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "file3");
+ archive_entry_set_mode(ae, S_IFREG | 0755);
+ create_reg_file3(ae, "Regular file with size restriction");
+ archive_entry_free(ae);
+
+ /* A regular file with an unspecified size */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "file3");
+ archive_entry_set_mode(ae, S_IFREG | 0755);
+ create_reg_file4(ae, "Regular file with unspecified size");
+ archive_entry_free(ae);
+
/* A regular file over an existing file */
assert((ae = archive_entry_new()) != NULL);
archive_entry_copy_pathname(ae, "file");
diff --git a/lib/libarchive/test/test_write_disk_hardlink.c b/lib/libarchive/test/test_write_disk_hardlink.c
index f8197c8..6e38d86 100644
--- a/lib/libarchive/test/test_write_disk_hardlink.c
+++ b/lib/libarchive/test/test_write_disk_hardlink.c
@@ -61,18 +61,48 @@ DEFINE_TEST(test_write_disk_hardlink)
archive_entry_set_mode(ae, S_IFREG | 0755);
archive_entry_set_size(ae, sizeof(data));
assertEqualIntA(ad, 0, archive_write_header(ad, ae));
- assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data)));
+ assertEqualInt(sizeof(data),
+ archive_write_data(ad, data, sizeof(data)));
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
archive_entry_free(ae);
- /* Link. */
+ /* Link. Size of zero means this doesn't carry data. */
assert((ae = archive_entry_new()) != NULL);
archive_entry_copy_pathname(ae, "link1b");
- archive_entry_set_mode(ae, S_IFREG | 0600);
+ archive_entry_set_mode(ae, S_IFREG | 0642);
archive_entry_set_size(ae, 0);
archive_entry_copy_hardlink(ae, "link1a");
assertEqualIntA(ad, 0, archive_write_header(ad, ae));
- assertEqualInt(0, archive_write_data(ad, data, sizeof(data)));
+ assertEqualInt(ARCHIVE_WARN,
+ archive_write_data(ad, data, sizeof(data)));
+ assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
+ archive_entry_free(ae);
+
+ /*
+ * Repeat tar approach test, but use unset to mark the
+ * hardlink as having no data.
+ */
+
+ /* Regular file. */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "link2a");
+ archive_entry_set_mode(ae, S_IFREG | 0755);
+ archive_entry_set_size(ae, sizeof(data));
+ assertEqualIntA(ad, 0, archive_write_header(ad, ae));
+ assertEqualInt(sizeof(data),
+ archive_write_data(ad, data, sizeof(data)));
+ assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
+ archive_entry_free(ae);
+
+ /* Link. Unset size means this doesn't carry data. */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "link2b");
+ archive_entry_set_mode(ae, S_IFREG | 0642);
+ archive_entry_unset_size(ae);
+ archive_entry_copy_hardlink(ae, "link2a");
+ assertEqualIntA(ad, 0, archive_write_header(ad, ae));
+ assertEqualInt(ARCHIVE_WARN,
+ archive_write_data(ad, data, sizeof(data)));
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
archive_entry_free(ae);
@@ -83,7 +113,7 @@ DEFINE_TEST(test_write_disk_hardlink)
/* Regular file. */
assert((ae = archive_entry_new()) != NULL);
- archive_entry_copy_pathname(ae, "link2a");
+ archive_entry_copy_pathname(ae, "link3a");
archive_entry_set_mode(ae, S_IFREG | 0600);
archive_entry_set_size(ae, sizeof(data));
assertEqualIntA(ad, 0, archive_write_header(ad, ae));
@@ -93,10 +123,10 @@ DEFINE_TEST(test_write_disk_hardlink)
/* Link. */
assert((ae = archive_entry_new()) != NULL);
- archive_entry_copy_pathname(ae, "link2b");
+ archive_entry_copy_pathname(ae, "link3b");
archive_entry_set_mode(ae, S_IFREG | 0755);
archive_entry_set_size(ae, sizeof(data));
- archive_entry_copy_hardlink(ae, "link2a");
+ archive_entry_copy_hardlink(ae, "link3a");
assertEqualIntA(ad, 0, archive_write_header(ad, ae));
assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data)));
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
@@ -109,7 +139,7 @@ DEFINE_TEST(test_write_disk_hardlink)
/* Regular file. */
assert((ae = archive_entry_new()) != NULL);
- archive_entry_copy_pathname(ae, "link3a");
+ archive_entry_copy_pathname(ae, "link4a");
archive_entry_set_mode(ae, S_IFREG | 0600);
archive_entry_set_size(ae, 0);
assertEqualIntA(ad, 0, archive_write_header(ad, ae));
@@ -123,10 +153,10 @@ DEFINE_TEST(test_write_disk_hardlink)
/* Link. */
assert((ae = archive_entry_new()) != NULL);
- archive_entry_copy_pathname(ae, "link3b");
+ archive_entry_copy_pathname(ae, "link4b");
archive_entry_set_mode(ae, S_IFREG | 0755);
archive_entry_set_size(ae, sizeof(data));
- archive_entry_copy_hardlink(ae, "link3a");
+ archive_entry_copy_hardlink(ae, "link4a");
assertEqualIntA(ad, 0, archive_write_header(ad, ae));
assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data)));
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
@@ -138,36 +168,63 @@ DEFINE_TEST(test_write_disk_hardlink)
#endif
/* Test the entries on disk. */
+
+ /* Test #1 */
assert(0 == stat("link1a", &st));
+ /* If the hardlink was successfully created and the archive
+ * doesn't carry data for it, we consider it to be
+ * non-authoritive for meta data as well. This is consistent
+ * with GNU tar and BSD pax. */
assertEqualInt(st.st_mode, (S_IFREG | 0755) & ~UMASK);
assertEqualInt(st.st_size, sizeof(data));
assertEqualInt(st.st_nlink, 2);
assert(0 == stat("link1b", &st2));
- assertEqualInt(st2.st_mode, (S_IFREG | 0755) & ~UMASK);
- assertEqualInt(st2.st_size, sizeof(data));
- assertEqualInt(st2.st_nlink, 2);
+ assertEqualInt(st.st_mode, st2.st_mode);
+ assertEqualInt(st.st_size, st2.st_size);
+ assertEqualInt(st.st_nlink, st2.st_nlink);
assertEqualInt(st.st_ino, st2.st_ino);
assertEqualInt(st.st_dev, st2.st_dev);
+ /* Test #2: Should produce identical results to test #1 */
+ /* Note that marking a hardlink with size = 0 is treated the
+ * same as having an unset size. This is partly for backwards
+ * compatibility (we used to not have unset tracking, so
+ * relied on size == 0) and partly to match the model used by
+ * common file formats that store a size of zero for
+ * hardlinks. */
assert(0 == stat("link2a", &st));
assertEqualInt(st.st_mode, (S_IFREG | 0755) & ~UMASK);
assertEqualInt(st.st_size, sizeof(data));
assertEqualInt(st.st_nlink, 2);
assert(0 == stat("link2b", &st2));
+ assertEqualInt(st.st_mode, st2.st_mode);
+ assertEqualInt(st.st_size, st2.st_size);
+ assertEqualInt(st.st_nlink, st2.st_nlink);
+ assertEqualInt(st.st_ino, st2.st_ino);
+ assertEqualInt(st.st_dev, st2.st_dev);
+
+ /* Test #3 */
+ assert(0 == stat("link3a", &st));
+ assertEqualInt(st.st_mode, (S_IFREG | 0755) & ~UMASK);
+ assertEqualInt(st.st_size, sizeof(data));
+ assertEqualInt(st.st_nlink, 2);
+
+ assert(0 == stat("link3b", &st2));
assertEqualInt(st2.st_mode, (S_IFREG | 0755) & ~UMASK);
assertEqualInt(st2.st_size, sizeof(data));
assertEqualInt(st2.st_nlink, 2);
assertEqualInt(st.st_ino, st2.st_ino);
assertEqualInt(st.st_dev, st2.st_dev);
- assert(0 == stat("link3a", &st));
+ /* Test #4 */
+ assert(0 == stat("link4a", &st));
assertEqualInt(st.st_mode, (S_IFREG | 0755) & ~UMASK);
assertEqualInt(st.st_size, sizeof(data));
assertEqualInt(st.st_nlink, 2);
- assert(0 == stat("link3b", &st2));
+ assert(0 == stat("link4b", &st2));
assertEqualInt(st2.st_mode, (S_IFREG | 0755) & ~UMASK);
assertEqualInt(st2.st_size, sizeof(data));
assertEqualInt(st2.st_nlink, 2);
OpenPOWER on IntegriCloud