diff options
author | kientzle <kientzle@FreeBSD.org> | 2008-09-30 04:02:36 +0000 |
---|---|---|
committer | kientzle <kientzle@FreeBSD.org> | 2008-09-30 04:02:36 +0000 |
commit | 1f6b640a7566f092993dbfa4bfddc1f4642afcaf (patch) | |
tree | 8fec41f9bde6c0c52474fa9988ecde4c4af04067 /lib/libarchive | |
parent | c7ce068f95b1e5db58283cf3ac1f5d8248816409 (diff) | |
download | FreeBSD-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/libarchive')
-rw-r--r-- | lib/libarchive/archive_write_disk.c | 204 | ||||
-rw-r--r-- | lib/libarchive/test/Makefile | 1 | ||||
-rw-r--r-- | lib/libarchive/test/test_write_disk.c | 1 | ||||
-rw-r--r-- | lib/libarchive/test/test_write_disk_times.c | 197 |
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, ×)); +} +#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, ×) != 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); +} |