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;
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);
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);
}
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->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
return (ARCHIVE_OK);
archive_clear_error(&a->archive);
- if (a->last_offset != a->filesize && a->fd >= 0) {
- if (ftruncate(a->fd, a->filesize) == -1 &&
- a->filesize == 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 {
+ /*
+ * The write handlers truncate long writes, so we
+ * never have to shorten a file here. Some systems
+ * can lengthen files with ftruncate(), but this is
+ * more portable:
+ */
+ const char nul = '\0';
+ if (lseek(a->fd, a->filesize - 1, SEEK_SET) < 0) {
+ archive_set_error(&a->archive, errno, "Seek failed");
+ return (ARCHIVE_FAILED);
+ }
+ if (write(a->fd, &nul, 1) < 0) {
archive_set_error(&a->archive, errno,
- "File size could not be restored");
+ "Write to restore size failed");
return (ARCHIVE_FAILED);
}
- /*
- * Explicitly stat the file as some platforms might not
- * implement the XSI option to extend files via ftruncate.
- */
a->pst = NULL;
- if ((ret = _archive_write_disk_lazy_stat(a)) != ARCHIVE_OK)
- return (ret);
- 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");
- return (ARCHIVE_FATAL);
- }
- if (write(a->fd, &nul, 1) < 0) {
- archive_set_error(&a->archive, errno,
- "Write to restore size failed");
- return (ARCHIVE_FATAL);
- }
- }
}
/* Restore metadata. */
#include "test.h"
__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_disk.c,v 1.9 2008/06/15 10:35:22 kientzle Exp $");
-#if ARCHIVE_VERSION_STAMP >= 1009000
-
#define UMASK 022
static void create(struct archive_entry *ae, const char *msg)
failure("%s", msg);
assertEqualIntA(ad, 0, archive_write_header(ad, ae));
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
-#if ARCHIVE_API_VERSION > 1
assertEqualInt(0, archive_write_finish(ad));
-#else
- archive_write_finish(ad);
-#endif
/* Test the entries on disk. */
assert(0 == stat(archive_entry_pathname(ae), &st));
failure("st.st_mode=%o archive_entry_mode(ae)=%o",
archive_write_data_block(ad, data + i, 1000, i));
}
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
-#if ARCHIVE_API_VERSION > 1
assertEqualInt(0, archive_write_finish(ad));
-#else
- archive_write_finish(ad);
-#endif
/* Test the entries on disk. */
assert(0 == stat(archive_entry_pathname(ae), &st));
failure("st.st_mode=%o archive_entry_mode(ae)=%o",
free(compare);
free(data);
}
-#endif
+
+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));
+ assertEqualInt(0, archive_write_finish(ad));
+ /* 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));
+ assertEqualInt(0, archive_write_finish(ad));
+ /* 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));
+}
+
DEFINE_TEST(test_write_disk)
{
-#if ARCHIVE_VERSION_STAMP < 1009000
- skipping("archive_write_disk interface");
-#else
struct archive_entry *ae;
/* Force the umask to something predictable. */
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");
archive_entry_set_mode(ae, S_IFREG | 0744);
create(ae, "Test creating a file over an existing dir.");
archive_entry_free(ae);
-#endif
}