summaryrefslogtreecommitdiffstats
path: root/lib/libarchive/archive_write_disk.c
diff options
context:
space:
mode:
authorkientzle <kientzle@FreeBSD.org>2008-01-18 05:05:58 +0000
committerkientzle <kientzle@FreeBSD.org>2008-01-18 05:05:58 +0000
commitfe18434231c3a45be41f61f97e2a9621b7170f9b (patch)
tree5165279ead289db42cecc95b1be0df8ba4812ccc /lib/libarchive/archive_write_disk.c
parent979a8f34d1fa93558f6c0947d03bb8cdaeada7b5 (diff)
downloadFreeBSD-src-fe18434231c3a45be41f61f97e2a9621b7170f9b.zip
FreeBSD-src-fe18434231c3a45be41f61f97e2a9621b7170f9b.tar.gz
Issues with hardlinks in newc-format files prompted me to
write a new test to exercise the hardlink strategies used by different archive formats (tar, old cpio, new cpio). This uncovered two problems, both fixed by this commit: 1) Enforce file size when writing files to disk. 2) When restoring hardlink entries, if they have data associated, go ahead and open the file so we can write the data. In particular, this fixes bsdtar/bsdcpio extraction of new cpio formats where the "original" is empty and the subsequent "hardlink" entry actually carries the data. It also provides correct behavior for old cpio archives where hardlinked entries have their bodies stored multiple times in the archive; the last body should always be the one that ends up in the final file. The new pax format also permits (but does not require) hardlinks to carry file data; again, the last contents should always win. Note that with any of these, a size of zero on a hardlink simply means that the hardlink carries no data; it does not mean that the file has zero size. A non-zero size on a hardlink does provide the file size. Thanks to: John Baldwin, for reminding me about this long-standing bug and sending me a simple example archive that prompted this test case
Diffstat (limited to 'lib/libarchive/archive_write_disk.c')
-rw-r--r--lib/libarchive/archive_write_disk.c38
1 files changed, 32 insertions, 6 deletions
diff --git a/lib/libarchive/archive_write_disk.c b/lib/libarchive/archive_write_disk.c
index 8b42811..f55eaf0 100644
--- a/lib/libarchive/archive_write_disk.c
+++ b/lib/libarchive/archive_write_disk.c
@@ -171,6 +171,8 @@ struct archive_write_disk {
int fd;
/* Current offset for writing data to the file. */
off_t offset;
+ /* Maximum size of file. */
+ off_t filesize;
/* Dir we were in before this restore; only for deep paths. */
int restore_pwd;
/* Mode we should use for this entry; affected by _PERM and umask. */
@@ -302,6 +304,7 @@ _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);
archive_strcpy(&(a->_name_data), archive_entry_pathname(a->entry));
a->name = a->_name_data.s;
archive_clear_error(&a->archive);
@@ -425,8 +428,10 @@ _archive_write_header(struct archive *_a, struct archive_entry *entry)
* If it's not open, tell our client not to try writing.
* In particular, dirs, links, etc, don't get written to.
*/
- if (a->fd < 0)
+ if (a->fd < 0) {
archive_entry_set_size(entry, 0);
+ a->filesize = 0;
+ }
done:
/* Restore the user's umask before returning. */
umask(a->user_umask);
@@ -451,6 +456,7 @@ _archive_write_data_block(struct archive *_a,
{
struct archive_write_disk *a = (struct archive_write_disk *)_a;
ssize_t bytes_written = 0;
+ int r = ARCHIVE_OK;
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
ARCHIVE_STATE_DATA, "archive_write_disk_block");
@@ -470,7 +476,13 @@ _archive_write_data_block(struct archive *_a,
}
/* Write the data. */
- while (size > 0) {
+ while (size > 0 && a->offset < a->filesize) {
+ if (a->offset + size > a->filesize) {
+ size = a->filesize - a->offset;
+ archive_set_error(&a->archive, errno,
+ "Write request too large");
+ r = ARCHIVE_WARN;
+ }
bytes_written = write(a->fd, buff, size);
if (bytes_written < 0) {
archive_set_error(&a->archive, errno, "Write failed");
@@ -479,13 +491,14 @@ _archive_write_data_block(struct archive *_a,
size -= bytes_written;
a->offset += bytes_written;
}
- return (ARCHIVE_OK);
+ return (r);
}
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;
+ off_t offset;
int r;
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
@@ -493,10 +506,11 @@ _archive_write_data(struct archive *_a, const void *buff, size_t size)
if (a->fd < 0)
return (ARCHIVE_OK);
+ offset = a->offset;
r = _archive_write_data_block(_a, buff, size, a->offset);
if (r < ARCHIVE_OK)
return (r);
- return (size);
+ return (a->offset - offset);
}
static int
@@ -829,8 +843,20 @@ create_filesystem_object(struct archive_write_disk *a)
/* We identify hard/symlinks according to the link names. */
/* Since link(2) and symlink(2) don't handle modes, we're done here. */
linkname = archive_entry_hardlink(a->entry);
- if (linkname != NULL)
- return link(linkname, a->name) ? errno : 0;
+ if (linkname != NULL) {
+ r = link(linkname, a->name) ? errno : 0;
+ /*
+ * New cpio and pax formats allow hardlink entries
+ * to carry data, so we may have to open the file
+ * for hardlink entries.
+ */
+ if (r == 0 && a->filesize > 0) {
+ a->fd = open(a->name, O_WRONLY | O_TRUNC);
+ if (a->fd < 0)
+ r = errno;
+ }
+ return (r);
+ }
linkname = archive_entry_symlink(a->entry);
if (linkname != NULL)
return symlink(linkname, a->name) ? errno : 0;
OpenPOWER on IntegriCloud