MfP4: restore birth time data to disk + more thorough tests for

time restore to disk.

MFC after:	30 days
This commit is contained in:
kientzle 2008-09-30 04:02:36 +00:00
parent c7ce068f95
commit 1f6b640a75
4 changed files with 324 additions and 87 deletions

View File

@ -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,20 +451,30 @@ _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_mtime_is_set(entry)) {
fe->mtime = archive_entry_mtime(entry);
fe->mtime_nanos = archive_entry_mtime_nsec(entry);
} else {
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);
} 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_birthtime_is_set(entry)) {
fe->birthtime = archive_entry_birthtime(entry);
fe->birthtime_nanos = archive_entry_birthtime_nsec(entry);
} else {
/* If birthtime is unset, use mtime. */
fe->birthtime = fe->mtime;
fe->birthtime_nanos = fe->mtime_nanos;
}
}
if (a->deferred & TODO_FFLAGS) {
@ -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,49 +1709,113 @@ 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];
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 (fd >= 0)
return (futimes(fd, times));
#else
(void)fd; /* UNUSED */
#endif
#ifdef HAVE_LUTIMES
(void)mode; /* UNUSED */
return (lutimes(name, times));
#else
if (S_ISLNK(mode))
return (0);
return (utimes(name, times));
#endif
}
#elif defined(HAVE_UTIME)
/*
* utime() is an older, more standard interface that we'll use
* if utimes() isn't available.
*/
static int
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... */
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;
atime = archive_entry_atime(a->entry);
atime_nsec = archive_entry_atime_nsec(a->entry);
}
#ifdef HAVE_FUTIMES
if (a->fd >= 0 && futimes(a->fd, times) == 0) {
return (ARCHIVE_OK);
}
/*
* 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
#ifdef HAVE_LUTIMES
if (lutimes(a->name, times) != 0)
#else
if (!S_ISLNK(a->mode) && utimes(a->name, times) != 0)
#endif
{
archive_set_error(&a->archive, errno, "Can't update time for %s",
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);
return (ARCHIVE_WARN);
}
@ -1740,56 +1826,8 @@ set_time(struct archive_write_disk *a)
* So, any restoration of ctime will necessarily be OS-specific.
*/
/* XXX TODO: Can FreeBSD restore ctime? XXX */
return (ARCHIVE_OK);
}
#elif defined(HAVE_UTIME)
/*
* utime() is an older, more standard interface that we'll use
* if utimes() isn't available.
*/
static int
set_time(struct archive_write_disk *a)
{
struct utimbuf times;
/* 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... */
/* 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;
/* 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 (!S_ISLNK(a->mode) && utime(a->name, &times) != 0) {
archive_set_error(&a->archive, errno,
"Can't update time for %s", a->name);
return (ARCHIVE_WARN);
}
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)

View File

@ -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 \

View File

@ -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);

View File

@ -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);
}