summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorkientzle <kientzle@FreeBSD.org>2008-09-30 04:02:36 +0000
committerkientzle <kientzle@FreeBSD.org>2008-09-30 04:02:36 +0000
commit1f6b640a7566f092993dbfa4bfddc1f4642afcaf (patch)
tree8fec41f9bde6c0c52474fa9988ecde4c4af04067 /lib
parentc7ce068f95b1e5db58283cf3ac1f5d8248816409 (diff)
downloadFreeBSD-src-1f6b640a7566f092993dbfa4bfddc1f4642afcaf.zip
FreeBSD-src-1f6b640a7566f092993dbfa4bfddc1f4642afcaf.tar.gz
MfP4: restore birth time data to disk + more thorough tests for
time restore to disk. MFC after: 30 days
Diffstat (limited to 'lib')
-rw-r--r--lib/libarchive/archive_write_disk.c204
-rw-r--r--lib/libarchive/test/Makefile1
-rw-r--r--lib/libarchive/test/test_write_disk.c1
-rw-r--r--lib/libarchive/test/test_write_disk_times.c197
4 files changed, 320 insertions, 83 deletions
diff --git a/lib/libarchive/archive_write_disk.c b/lib/libarchive/archive_write_disk.c
index 19c3199..016fec4 100644
--- a/lib/libarchive/archive_write_disk.c
+++ b/lib/libarchive/archive_write_disk.c
@@ -96,10 +96,12 @@ __FBSDID("$FreeBSD$");
struct fixup_entry {
struct fixup_entry *next;
mode_t mode;
- int64_t mtime;
int64_t atime;
- unsigned long mtime_nanos;
+ int64_t birthtime;
+ int64_t mtime;
unsigned long atime_nanos;
+ unsigned long birthtime_nanos;
+ unsigned long mtime_nanos;
unsigned long fflags_set;
int fixup; /* bitmask of what needs fixing */
char *name;
@@ -227,7 +229,8 @@ static int set_fflags_platform(struct archive_write_disk *, int fd,
unsigned long fflags_set, unsigned long fflags_clear);
static int set_ownership(struct archive_write_disk *);
static int set_mode(struct archive_write_disk *, int mode);
-static int set_time(struct archive_write_disk *);
+static int set_time(int, int, const char *, time_t, long, time_t, long);
+static int set_times(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);
@@ -448,19 +451,29 @@ _archive_write_header(struct archive *_a, struct archive_entry *entry)
|| archive_entry_atime_is_set(entry))) {
fe = current_fixup(a, archive_entry_pathname(entry));
fe->fixup |= TODO_TIMES;
+ if (archive_entry_atime_is_set(entry)) {
+ fe->atime = archive_entry_atime(entry);
+ fe->atime_nanos = archive_entry_atime_nsec(entry);
+ } else {
+ /* If atime is unset, use start time. */
+ fe->atime = a->start_time;
+ fe->atime_nanos = 0;
+ }
if (archive_entry_mtime_is_set(entry)) {
fe->mtime = archive_entry_mtime(entry);
fe->mtime_nanos = archive_entry_mtime_nsec(entry);
} else {
+ /* If mtime is unset, use start time. */
fe->mtime = a->start_time;
fe->mtime_nanos = 0;
}
- if (archive_entry_atime_is_set(entry)) {
- fe->atime = archive_entry_atime(entry);
- fe->atime_nanos = archive_entry_atime_nsec(entry);
+ if (archive_entry_birthtime_is_set(entry)) {
+ fe->birthtime = archive_entry_birthtime(entry);
+ fe->birthtime_nanos = archive_entry_birthtime_nsec(entry);
} else {
- fe->atime = a->start_time;
- fe->atime_nanos = 0;
+ /* If birthtime is unset, use mtime. */
+ fe->birthtime = fe->mtime;
+ fe->birthtime_nanos = fe->mtime_nanos;
}
}
@@ -698,7 +711,7 @@ _archive_write_finish_entry(struct archive *_a)
if (r2 < ret) ret = r2;
}
if (a->todo & TODO_TIMES) {
- int r2 = set_time(a);
+ int r2 = set_times(a);
if (r2 < ret) ret = r2;
}
if (a->todo & TODO_ACLS) {
@@ -1170,10 +1183,19 @@ _archive_write_close(struct archive *_a)
#ifdef HAVE_UTIMES
/* {f,l,}utimes() are preferred, when available. */
struct timeval times[2];
- times[1].tv_sec = p->mtime;
- times[1].tv_usec = p->mtime_nanos / 1000;
times[0].tv_sec = p->atime;
times[0].tv_usec = p->atime_nanos / 1000;
+#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME
+ /* if it's valid and not mtime, push the birthtime first */
+ if (((times[1].tv_sec = p->birthtime) < p->mtime) &&
+ (p->birthtime > 0))
+ {
+ times[1].tv_usec = p->birthtime_nanos / 1000;
+ utimes(p->name, times);
+ }
+#endif
+ times[1].tv_sec = p->mtime;
+ times[1].tv_usec = p->mtime_nanos / 1000;
#ifdef HAVE_LUTIMES
lutimes(p->name, times);
#else
@@ -1687,61 +1709,31 @@ set_ownership(struct archive_write_disk *a)
* when they're available.
*/
static int
-set_time(struct archive_write_disk *a)
+set_time(int fd, int mode, const char *name,
+ time_t atime, long atime_nsec,
+ time_t mtime, long mtime_nsec)
{
struct timeval times[2];
- /* If no time was provided, we're done. */
- if (!archive_entry_atime_is_set(a->entry)
- && !archive_entry_mtime_is_set(a->entry))
- return (ARCHIVE_OK);
-
- /* We know at least one is set, so... */
- if (archive_entry_mtime_is_set(a->entry)) {
- times[1].tv_sec = archive_entry_mtime(a->entry);
- times[1].tv_usec = archive_entry_mtime_nsec(a->entry) / 1000;
- } else {
- times[1].tv_sec = a->start_time;
- times[1].tv_usec = 0;
- }
-
- /* If no atime was specified, use start time instead. */
- /* In theory, it would be marginally more correct to use
- * time(NULL) here, but that would cost us an extra syscall
- * for little gain. */
- if (archive_entry_atime_is_set(a->entry)) {
- times[0].tv_sec = archive_entry_atime(a->entry);
- times[0].tv_usec = archive_entry_atime_nsec(a->entry) / 1000;
- } else {
- times[0].tv_sec = a->start_time;
- times[0].tv_usec = 0;
- }
+ times[0].tv_sec = atime;
+ times[0].tv_usec = atime_nsec / 1000;
+ times[1].tv_sec = mtime;
+ times[1].tv_usec = mtime_nsec / 1000;
#ifdef HAVE_FUTIMES
- if (a->fd >= 0 && futimes(a->fd, times) == 0) {
- return (ARCHIVE_OK);
- }
+ if (fd >= 0)
+ return (futimes(fd, times));
+#else
+ (void)fd; /* UNUSED */
#endif
-
#ifdef HAVE_LUTIMES
- if (lutimes(a->name, times) != 0)
+ (void)mode; /* UNUSED */
+ return (lutimes(name, times));
#else
- if (!S_ISLNK(a->mode) && utimes(a->name, times) != 0)
+ if (S_ISLNK(mode))
+ return (0);
+ return (utimes(name, times));
#endif
- {
- archive_set_error(&a->archive, errno, "Can't update time for %s",
- a->name);
- return (ARCHIVE_WARN);
- }
-
- /*
- * Note: POSIX does not provide a portable way to restore ctime.
- * (Apart from resetting the system clock, which is distasteful.)
- * So, any restoration of ctime will necessarily be OS-specific.
- */
-
- /* XXX TODO: Can FreeBSD restore ctime? XXX */
- return (ARCHIVE_OK);
}
#elif defined(HAVE_UTIME)
/*
@@ -1749,47 +1741,93 @@ set_time(struct archive_write_disk *a)
* if utimes() isn't available.
*/
static int
-set_time(struct archive_write_disk *a)
+set_time(int fd, int mode, const char *name,
+ time_t atime, long atime_nsec,
+ time_t mtime, long mtime_nsec)
{
struct utimbuf times;
+ (void)fd; /* UNUSED */
+ (void)name; /* UNUSED */
+ (void)atime_nsec; /* UNUSED */
+ (void)mtime_nsec; /* UNUSED */
+ times.actime = atime;
+ times.modtime = mtime;
+ if (S_ISLINK(mode))
+ return (ARCHIVE_OK);
+ return (utime(name, &times));
+}
+#else
+static int
+set_time(int fd, int mode, const char *name,
+ time_t atime, long atime_nsec,
+ time_t mtime, long mtime_nsec)
+{
+ return (ARCHIVE_WARN);
+}
+#endif
+
+static int
+set_times(struct archive_write_disk *a)
+{
+ time_t atime = a->start_time, mtime = a->start_time;
+ long atime_nsec = 0, mtime_nsec = 0;
/* If no time was provided, we're done. */
if (!archive_entry_atime_is_set(a->entry)
+#if HAVE_STRUCT_STAT_ST_BIRTHTIME
+ && !archive_entry_birthtime_is_set(a->entry)
+#endif
&& !archive_entry_mtime_is_set(a->entry))
return (ARCHIVE_OK);
- /* We know at least one is set, so... */
- /* Set mtime from mtime if set, else start time. */
- if (archive_entry_mtime_is_set(a->entry))
- times.modtime = archive_entry_mtime(a->entry);
- else
- times.modtime = a->start_time;
+ /* If no atime was specified, use start time instead. */
+ /* In theory, it would be marginally more correct to use
+ * time(NULL) here, but that would cost us an extra syscall
+ * for little gain. */
+ if (archive_entry_atime_is_set(a->entry)) {
+ atime = archive_entry_atime(a->entry);
+ atime_nsec = archive_entry_atime_nsec(a->entry);
+ }
- /* Set atime from provided atime, else mtime. */
- if (archive_entry_atime_is_set(a->entry))
- times.actime = archive_entry_atime(a->entry);
- else
- times.actime = a->start_time;
+ /*
+ * If you have struct stat.st_birthtime, we assume BSD birthtime
+ * semantics, in which {f,l,}utimes() updates birthtime to earliest
+ * mtime. So we set the time twice, first using the birthtime,
+ * then using the mtime.
+ */
+#if HAVE_STRUCT_STAT_ST_BIRTHTIME
+ /* If birthtime is set, flush that through to disk first. */
+ if (archive_entry_birthtime_is_set(a->entry))
+ if (set_time(a->fd, a->mode, a->name, atime, atime_nsec,
+ archive_entry_birthtime(a->entry),
+ archive_entry_birthtime_nsec(a->entry))) {
+ archive_set_error(&a->archive, errno,
+ "Can't update time for %s",
+ a->name);
+ return (ARCHIVE_WARN);
+ }
+#endif
- if (!S_ISLNK(a->mode) && utime(a->name, &times) != 0) {
+ if (archive_entry_mtime_is_set(a->entry)) {
+ mtime = archive_entry_mtime(a->entry);
+ mtime_nsec = archive_entry_mtime_nsec(a->entry);
+ }
+ if (set_time(a->fd, a->mode, a->name,
+ atime, atime_nsec, mtime, mtime_nsec)) {
archive_set_error(&a->archive, errno,
- "Can't update time for %s", a->name);
+ "Can't update time for %s",
+ a->name);
return (ARCHIVE_WARN);
}
+
+ /*
+ * Note: POSIX does not provide a portable way to restore ctime.
+ * (Apart from resetting the system clock, which is distasteful.)
+ * So, any restoration of ctime will necessarily be OS-specific.
+ */
+
return (ARCHIVE_OK);
}
-#else
-/* This platform doesn't give us a way to restore the time. */
-static int
-set_time(struct archive_write_disk *a)
-{
- (void)a; /* UNUSED */
- archive_set_error(&a->archive, errno,
- "Can't update time for %s", a->name);
- return (ARCHIVE_WARN);
-}
-#endif
-
static int
set_mode(struct archive_write_disk *a, int mode)
diff --git a/lib/libarchive/test/Makefile b/lib/libarchive/test/Makefile
index 30b86a4..1b9e205 100644
--- a/lib/libarchive/test/Makefile
+++ b/lib/libarchive/test/Makefile
@@ -57,6 +57,7 @@ TESTS= \
test_write_disk_hardlink.c \
test_write_disk_perms.c \
test_write_disk_secure.c \
+ test_write_disk_times.c \
test_write_format_ar.c \
test_write_format_cpio.c \
test_write_format_cpio_empty.c \
diff --git a/lib/libarchive/test/test_write_disk.c b/lib/libarchive/test/test_write_disk.c
index b91288b..ae87c77 100644
--- a/lib/libarchive/test/test_write_disk.c
+++ b/lib/libarchive/test/test_write_disk.c
@@ -99,6 +99,7 @@ static void create_reg_file(struct archive_entry *ae, const char *msg)
st.st_mode, archive_entry_mode(ae));
assertEqualInt(st.st_mode, (archive_entry_mode(ae) & ~UMASK));
assertEqualInt(st.st_size, sizeof(data));
+ /* test_write_disk_times has more detailed tests of this area. */
assertEqualInt(st.st_mtime, 123456789);
failure("No atime was specified, so atime should get set to current time");
now = time(NULL);
diff --git a/lib/libarchive/test/test_write_disk_times.c b/lib/libarchive/test/test_write_disk_times.c
new file mode 100644
index 0000000..2891e36
--- /dev/null
+++ b/lib/libarchive/test/test_write_disk_times.c
@@ -0,0 +1,197 @@
+/*-
+ * Copyright (c) 2003-2008 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+__FBSDID("$FreeBSD$");
+
+/*
+ * Exercise time restores in archive_write_disk(), including
+ * correct handling of omitted time values.
+ * On FreeBSD, we also test birthtime and high-res time restores.
+ */
+
+DEFINE_TEST(test_write_disk_times)
+{
+ struct archive *a;
+ struct archive_entry *ae;
+ struct stat st;
+ time_t now = time(NULL);
+
+ /* Create an archive_write_disk object. */
+ assert((a = archive_write_disk_new()) != NULL);
+ assertEqualInt(ARCHIVE_OK,
+ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_TIME));
+
+ /*
+ * Easy case: mtime and atime both specified.
+ */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "file1");
+ archive_entry_set_mode(ae, S_IFREG | 0777);
+ archive_entry_set_atime(ae, 123456, 0);
+ archive_entry_set_mtime(ae, 234567, 0);
+ assertEqualInt(ARCHIVE_OK, archive_write_header(a, ae));
+ assertEqualInt(ARCHIVE_OK, archive_write_finish_entry(a));
+ archive_entry_free(ae);
+ /* Verify */
+ assertEqualInt(0, stat("file1", &st));
+ assertEqualInt(123456, st.st_atime);
+ assertEqualInt(234567, st.st_mtime);
+
+ /*
+ * mtime specified, but not atime
+ */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "file2");
+ archive_entry_set_mode(ae, S_IFREG | 0777);
+ archive_entry_set_mtime(ae, 234567, 0);
+ assertEqualInt(ARCHIVE_OK, archive_write_header(a, ae));
+ assertEqualInt(ARCHIVE_OK, archive_write_finish_entry(a));
+ archive_entry_free(ae);
+ /* Verify: Current atime and mtime as specified. */
+ assertEqualInt(0, stat("file2", &st));
+ assertEqualInt(234567, st.st_mtime);
+ failure("now: %ld st.st_atime: %ld", (long)now, (long)st.st_atime);
+ assert(st.st_atime >= now && st.st_atime < now + 3);
+
+ /*
+ * atime specified, but not mtime
+ */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "file3");
+ archive_entry_set_mode(ae, S_IFREG | 0777);
+ archive_entry_set_atime(ae, 345678, 0);
+ assertEqualInt(ARCHIVE_OK, archive_write_header(a, ae));
+ assertEqualInt(ARCHIVE_OK, archive_write_finish_entry(a));
+ archive_entry_free(ae);
+ /* Verify: Current mtime and atime as specified. */
+ assertEqualInt(0, stat("file3", &st));
+ assertEqualInt(345678, st.st_atime);
+ failure("now: %ld st.st_mtime: %ld", (long)now, (long)st.st_mtime);
+ assert(st.st_mtime >= now && st.st_mtime < now + 3);
+
+ /*
+ * Neither atime nor mtime specified.
+ */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "file4");
+ archive_entry_set_mode(ae, S_IFREG | 0777);
+ assertEqualInt(ARCHIVE_OK, archive_write_header(a, ae));
+ assertEqualInt(ARCHIVE_OK, archive_write_finish_entry(a));
+ archive_entry_free(ae);
+ /* Verify: Current mtime and atime. */
+ assertEqualInt(0, stat("file4", &st));
+ failure("now: %ld st.st_atime: %ld", (long)now, (long)st.st_atime);
+ assert(st.st_atime >= now && st.st_atime < now + 3);
+ failure("now: %ld st.st_mtime: %ld", (long)now, (long)st.st_mtime);
+ assert(st.st_mtime >= now && st.st_mtime < now + 3);
+
+#if defined(__FreeBSD__)
+ /*
+ * High-res mtime and atime on FreeBSD.
+ */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "file10");
+ archive_entry_set_mode(ae, S_IFREG | 0777);
+ archive_entry_set_atime(ae, 1234567, 23456);
+ archive_entry_set_mtime(ae, 2345678, 4567);
+ assertEqualInt(ARCHIVE_OK, archive_write_header(a, ae));
+ assertEqualInt(ARCHIVE_OK, archive_write_finish_entry(a));
+ archive_entry_free(ae);
+ /* Verify */
+ /* FreeBSD can only store usec resolution, hence rounding here. */
+ assertEqualInt(0, stat("file10", &st));
+ assertEqualInt(1234567, st.st_atime);
+ assertEqualInt(23000, st.st_atimespec.tv_nsec);
+ assertEqualInt(2345678, st.st_mtime);
+ assertEqualInt(4000, st.st_mtimespec.tv_nsec);
+
+ /*
+ * Birthtime, mtime and atime on FreeBSD
+ */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "file11");
+ archive_entry_set_mode(ae, S_IFREG | 0777);
+ archive_entry_set_atime(ae, 1234567, 23456);
+ archive_entry_set_birthtime(ae, 3456789, 12345);
+ /* mtime must be later than birthtime! */
+ archive_entry_set_mtime(ae, 12345678, 4567);
+ assertEqualInt(ARCHIVE_OK, archive_write_header(a, ae));
+ assertEqualInt(ARCHIVE_OK, archive_write_finish_entry(a));
+ archive_entry_free(ae);
+ /* Verify */
+ /* FreeBSD can only store usec resolution, hence rounding here. */
+ assertEqualInt(0, stat("file11", &st));
+ assertEqualInt(1234567, st.st_atime);
+ assertEqualInt(23000, st.st_atimespec.tv_nsec);
+ assertEqualInt(3456789, st.st_birthtime);
+ assertEqualInt(12000, st.st_birthtimespec.tv_nsec);
+ assertEqualInt(12345678, st.st_mtime);
+ assertEqualInt(4000, st.st_mtimespec.tv_nsec);
+
+ /*
+ * Birthtime only on FreeBSD.
+ */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "file12");
+ archive_entry_set_mode(ae, S_IFREG | 0777);
+ archive_entry_set_birthtime(ae, 3456789, 12345);
+ assertEqualInt(ARCHIVE_OK, archive_write_header(a, ae));
+ assertEqualInt(ARCHIVE_OK, archive_write_finish_entry(a));
+ archive_entry_free(ae);
+ /* Verify */
+ /* FreeBSD can only store usec resolution, hence rounding here. */
+ assertEqualInt(0, stat("file12", &st));
+ assertEqualInt(3456789, st.st_birthtime);
+ assertEqualInt(12000, st.st_birthtimespec.tv_nsec);
+ failure("now: %ld st.st_atime: %ld", (long)now, (long)st.st_atime);
+ assert(st.st_atime >= now && st.st_atime < now + 3);
+ failure("now: %ld st.st_mtime: %ld", (long)now, (long)st.st_mtime);
+ assert(st.st_mtime >= now && st.st_mtime < now + 3);
+
+ /*
+ * mtime only on FreeBSD.
+ */
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, "file13");
+ archive_entry_set_mode(ae, S_IFREG | 0777);
+ archive_entry_set_mtime(ae, 4567890, 23456);
+ assertEqualInt(ARCHIVE_OK, archive_write_header(a, ae));
+ assertEqualInt(ARCHIVE_OK, archive_write_finish_entry(a));
+ archive_entry_free(ae);
+ /* Verify */
+ /* FreeBSD can only store usec resolution, hence rounding here. */
+ assertEqualInt(0, stat("file13", &st));
+ assertEqualInt(4567890, st.st_birthtime);
+ assertEqualInt(23000, st.st_birthtimespec.tv_nsec);
+ assertEqualInt(4567890, st.st_mtime);
+ assertEqualInt(23000, st.st_mtimespec.tv_nsec);
+ failure("now: %ld st.st_atime: %ld", (long)now, (long)st.st_atime);
+ assert(st.st_atime >= now && st.st_atime < now + 3);
+#else
+ skipping("Platform-specific time restore tests");
+#endif
+
+ archive_write_finish(a);
+}
OpenPOWER on IntegriCloud