X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fincremen.c;h=e4645fa1f16a5b007b3603672f220e9531c34e96;hb=ae00dc0d37baef4504408c9ce03997781b735d41;hp=facc751c4bc20dbc9fb7a05d2055f9d62087d31c;hpb=e8af2fec8a7ac51bc8a80586566b342044119fe8;p=chaz%2Ftar diff --git a/src/incremen.c b/src/incremen.c index facc751..e4645fa 100644 --- a/src/incremen.c +++ b/src/incremen.c @@ -1,7 +1,7 @@ /* GNU dump extensions to tar. Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001, - 2003, 2004 Free Software Foundation, Inc. + 2003, 2004, 2005 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the @@ -15,16 +15,13 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., - 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "system.h" +#include #include #include #include #include "common.h" -#define obstack_chunk_alloc xmalloc -#define obstack_chunk_free free -#include /* Incremental dump specialities. */ @@ -34,12 +31,13 @@ enum children {NO_CHILDREN, CHANGED_CHILDREN, ALL_CHILDREN}; /* Directory attributes. */ struct directory { + struct timespec mtime; /* Modification time */ dev_t device_number; /* device number for directory */ ino_t inode_number; /* inode number for directory */ enum children children; bool nfs; bool found; - char name[1]; /* path name of directory */ + char name[1]; /* file name of directory */ }; static Hash_table *directory_table; @@ -53,8 +51,8 @@ static Hash_table *directory_table; #endif /* Calculate the hash of a directory. */ -static unsigned -hash_directory (void const *entry, unsigned n_buckets) +static size_t +hash_directory (void const *entry, size_t n_buckets) { struct directory const *directory = entry; return hash_string (directory->name, n_buckets); @@ -74,11 +72,13 @@ compare_directories (void const *entry1, void const *entry2) whether it is an NFS device and FOUND indicating whether we have found that the directory exists. */ static struct directory * -note_directory (char const *name, dev_t dev, ino_t ino, bool nfs, bool found) +note_directory (char const *name, struct timespec mtime, + dev_t dev, ino_t ino, bool nfs, bool found) { size_t size = offsetof (struct directory, name) + strlen (name) + 1; struct directory *directory = xmalloc (size); + directory->mtime = mtime; directory->device_number = dev; directory->inode_number = ino; directory->children = CHANGED_CHILDREN; @@ -95,7 +95,7 @@ note_directory (char const *name, dev_t dev, ino_t ino, bool nfs, bool found) return directory; } -/* Return a directory entry for a given path NAME, or zero if none found. */ +/* Return a directory entry for a given file NAME, or zero if none found. */ static struct directory * find_directory (char *name) { @@ -110,6 +110,31 @@ find_directory (char *name) } } +void +update_parent_directory (const char *name) +{ + struct directory *directory; + char *p, *name_buffer; + + p = dir_name (name); + name_buffer = xmalloc (strlen (p) + 2); + strcpy (name_buffer, p); + if (! ISSLASH (p[strlen (p) - 1])) + strcat (name_buffer, "/"); + + directory = find_directory (name_buffer); + free (name_buffer); + if (directory) + { + struct stat st; + if (deref_stat (dereference_option, p, &st) != 0) + stat_diag (name); + else + directory->mtime = get_stat_mtime (&st); + } + free (p); +} + static int compare_dirents (const void *first, const void *second) { @@ -117,34 +142,106 @@ compare_dirents (const void *first, const void *second) (*(char *const *) second) + 1); } -/* Recursively scan the given PATH. */ +enum children +procdir (char *name_buffer, struct stat *stat_data, + dev_t device, + enum children children, + bool verbose) +{ + struct directory *directory; + bool nfs = NFS_FILE_STAT (*stat_data); + + if ((directory = find_directory (name_buffer)) != NULL) + { + /* With NFS, the same file can have two different devices + if an NFS directory is mounted in multiple locations, + which is relatively common when automounting. + To avoid spurious incremental redumping of + directories, consider all NFS devices as equal, + relying on the i-node to establish differences. */ + + if (! (((directory->nfs & nfs) + || directory->device_number == stat_data->st_dev) + && directory->inode_number == stat_data->st_ino)) + { + if (verbose) + WARN ((0, 0, _("%s: Directory has been renamed"), + quotearg_colon (name_buffer))); + directory->children = ALL_CHILDREN; + directory->nfs = nfs; + directory->device_number = stat_data->st_dev; + directory->inode_number = stat_data->st_ino; + } + else if (listed_incremental_option) + /* Newer modification time can mean that new files were + created in the directory or some of the existing files + were renamed. */ + directory->children = + timespec_cmp (get_stat_mtime (stat_data), directory->mtime) > 0 + ? ALL_CHILDREN : CHANGED_CHILDREN; + + directory->found = true; + } + else + { + if (verbose) + WARN ((0, 0, _("%s: Directory is new"), + quotearg_colon (name_buffer))); + directory = note_directory (name_buffer, + get_stat_mtime(stat_data), + stat_data->st_dev, + stat_data->st_ino, + nfs, + true); + + directory->children = + (listed_incremental_option + || (OLDER_STAT_TIME (*stat_data, m) + || (after_date_option + && OLDER_STAT_TIME (*stat_data, c)))) + ? ALL_CHILDREN + : CHANGED_CHILDREN; + } + + if (one_file_system_option && device != stat_data->st_dev) + directory->children = NO_CHILDREN; + else if (children == ALL_CHILDREN) + directory->children = ALL_CHILDREN; + + return directory->children; +} + + +/* Recursively scan the given directory. */ static void -scan_path (struct obstack *stk, char *path, dev_t device) +scan_directory (struct obstack *stk, char *dir_name, dev_t device) { - char *dirp = savedir (path); /* for scanning directory */ + char *dirp = savedir (dir_name); /* for scanning directory */ char const *entry; /* directory entry being scanned */ size_t entrylen; /* length of directory entry */ char *name_buffer; /* directory, `/', and directory member */ size_t name_buffer_size; /* allocated size of name_buffer, minus 2 */ size_t name_length; /* used length in name_buffer */ - struct directory *directory; /* for checking if already seen */ enum children children; + struct stat stat_data; if (! dirp) - { - savedir_error (path); - } - errno = 0; + savedir_error (dir_name); - name_buffer_size = strlen (path) + NAME_FIELD_SIZE; + name_buffer_size = strlen (dir_name) + NAME_FIELD_SIZE; name_buffer = xmalloc (name_buffer_size + 2); - strcpy (name_buffer, path); - if (! ISSLASH (path[strlen (path) - 1])) + strcpy (name_buffer, dir_name); + if (! ISSLASH (dir_name[strlen (dir_name) - 1])) strcat (name_buffer, "/"); name_length = strlen (name_buffer); - directory = find_directory (path); - children = directory ? directory->children : CHANGED_CHILDREN; + if (deref_stat (dereference_option, name_buffer, &stat_data)) + { + stat_diag (name_buffer); + children = CHANGED_CHILDREN; + } + else + children = procdir (name_buffer, &stat_data, device, NO_CHILDREN, false); if (dirp && children != NO_CHILDREN) for (entry = dirp; @@ -159,68 +256,22 @@ scan_path (struct obstack *stk, char *path, dev_t device) name_buffer = xrealloc (name_buffer, name_buffer_size + 2); } strcpy (name_buffer + name_length, entry); - + if (excluded_name (name_buffer)) obstack_1grow (stk, 'N'); else { - struct stat stat_data; - + if (deref_stat (dereference_option, name_buffer, &stat_data)) { stat_diag (name_buffer); continue; } - + if (S_ISDIR (stat_data.st_mode)) { - bool nfs = NFS_FILE_STAT (stat_data); - - if ((directory = find_directory (name_buffer)) != NULL) - { - /* With NFS, the same file can have two different devices - if an NFS directory is mounted in multiple locations, - which is relatively common when automounting. - To avoid spurious incremental redumping of - directories, consider all NFS devices as equal, - relying on the i-node to establish differences. */ - - if (! (((directory->nfs & nfs) - || directory->device_number == stat_data.st_dev) - && directory->inode_number == stat_data.st_ino)) - { - if (verbose_option) - WARN ((0, 0, _("%s: Directory has been renamed"), - quotearg_colon (name_buffer))); - directory->children = ALL_CHILDREN; - directory->nfs = nfs; - directory->device_number = stat_data.st_dev; - directory->inode_number = stat_data.st_ino; - } - directory->found = 1; - } - else - { - if (verbose_option) - WARN ((0, 0, _("%s: Directory is new"), - quotearg_colon (name_buffer))); - directory = note_directory (name_buffer, - stat_data.st_dev, - stat_data.st_ino, nfs, 1); - directory->children = - ((listed_incremental_option - || newer_mtime_option <= stat_data.st_mtime - || (after_date_option && - newer_ctime_option <= stat_data.st_ctime)) - ? ALL_CHILDREN - : CHANGED_CHILDREN); - } - - if (one_file_system_option && device != stat_data.st_dev) - directory->children = NO_CHILDREN; - else if (children == ALL_CHILDREN) - directory->children = ALL_CHILDREN; - + procdir (name_buffer, &stat_data, device, children, + verbose_option); obstack_1grow (stk, 'D'); } @@ -239,19 +290,18 @@ scan_path (struct obstack *stk, char *path, dev_t device) else if (children == CHANGED_CHILDREN - && stat_data.st_mtime < newer_mtime_option - && (!after_date_option - || stat_data.st_ctime < newer_ctime_option)) + && OLDER_STAT_TIME (stat_data, m) + && (!after_date_option || OLDER_STAT_TIME (stat_data, c))) obstack_1grow (stk, 'N'); else obstack_1grow (stk, 'Y'); } - + obstack_grow (stk, entry, entrylen + 1); } obstack_grow (stk, "\000\000", 2); - + free (name_buffer); if (dirp) free (dirp); @@ -267,16 +317,16 @@ sort_obstack (struct obstack *stk) char *buffer; char **array; char **array_cursor; - + counter = 0; for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1) counter++; - + if (!counter) return NULL; array = obstack_alloc (stk, sizeof (char *) * (counter + 1)); - + array_cursor = array; for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1) *array_cursor++ = cursor; @@ -290,7 +340,7 @@ sort_obstack (struct obstack *stk) for (array_cursor = array; *array_cursor; array_cursor++) { char *string = *array_cursor; - + while ((*cursor++ = *string++)) continue; } @@ -299,22 +349,52 @@ sort_obstack (struct obstack *stk) } char * -get_directory_contents (char *path, dev_t device) +get_directory_contents (char *dir_name, dev_t device) { struct obstack stk; char *buffer; obstack_init (&stk); - scan_path (&stk, path, device); + scan_directory (&stk, dir_name, device); buffer = sort_obstack (&stk); obstack_free (&stk, NULL); return buffer; } +size_t +dumpdir_size (const char *p) +{ + size_t totsize = 0; + + while (*p) + { + size_t size = strlen (p) + 1; + totsize += size; + p += size; + } + return totsize + 1; +} + static FILE *listed_incremental_stream; +/* Version of incremental format snapshots (directory files) used by this + tar. Currently it is supposed to be a single decimal number. 0 means + incremental snapshots as per tar version before 1.15.2. + + The current tar version supports incremental versions from + 0 up to TAR_INCREMENTAL_VERSION, inclusive. + It is able to create only snapshots of TAR_INCREMENTAL_VERSION */ + +#define TAR_INCREMENTAL_VERSION 1 + +/* Read incremental snapshot file (directory file). + If the file has older incremental version, make sure that it is processed + correctly and that tar will use the most conservative backup method among + possible alternatives (i.e. prefer ALL_CHILDREN over CHANGED_CHILDREN, + etc.) This ensures that the snapshots are updated to the recent version + without any loss of data. */ void read_directory_file (void) { @@ -348,16 +428,69 @@ read_directory_file (void) char *ebuf; int n; long lineno = 1; - unsigned long u = (errno = 0, strtoul (buf, &ebuf, 10)); + uintmax_t u; time_t t = u; + int incremental_version; + + if (strncmp (buf, PACKAGE_NAME, sizeof PACKAGE_NAME - 1) == 0) + { + ebuf = buf + sizeof PACKAGE_NAME - 1; + if (*ebuf++ != '-') + ERROR((1, 0, _("Bad incremental file format"))); + for (; *ebuf != '-'; ebuf++) + if (!*ebuf) + ERROR((1, 0, _("Bad incremental file format"))); + + incremental_version = (errno = 0, strtoumax (ebuf+1, &ebuf, 10)); + if (getline (&buf, &bufsize, fp) <= 0) + { + read_error (listed_incremental_option); + free (buf); + return; + } + ++lineno; + } + else + incremental_version = 0; + + if (incremental_version > TAR_INCREMENTAL_VERSION) + ERROR((1, 0, _("Unsupported incremental format version: %s"), + incremental_version)); + + t = u = (errno = 0, strtoumax (buf, &ebuf, 10)); if (buf == ebuf || (u == 0 && errno == EINVAL)) - ERROR ((0, 0, "%s:1: %s", quotearg_colon (listed_incremental_option), + ERROR ((0, 0, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), + lineno, _("Invalid time stamp"))); - else if (t != u || (u == -1 && errno == ERANGE)) - ERROR ((0, 0, "%s:1: %s", quotearg_colon (listed_incremental_option), + else if (t != u) + ERROR ((0, 0, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), + lineno, _("Time stamp out of range"))); + else if (incremental_version == 1) + { + newer_mtime_option.tv_sec = t; + + t = u = (errno = 0, strtoumax (buf, &ebuf, 10)); + if (buf == ebuf || (u == 0 && errno == EINVAL)) + ERROR ((0, 0, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), + lineno, + _("Invalid time stamp"))); + else if (t != u) + ERROR ((0, 0, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), + lineno, + _("Time stamp out of range"))); + newer_mtime_option.tv_nsec = t; + } else - newer_mtime_option = t; + { + /* pre-1 incremental format does not contain nanoseconds */ + newer_mtime_option.tv_sec = t; + newer_mtime_option.tv_nsec = 0; + } while (0 < (n = getline (&buf, &bufsize, fp))) { @@ -365,31 +498,61 @@ read_directory_file (void) ino_t ino; bool nfs = buf[0] == '+'; char *strp = buf + nfs; + struct timespec mtime; lineno++; if (buf[n - 1] == '\n') buf[n - 1] = '\0'; + if (incremental_version == 1) + { + errno = 0; + mtime.tv_sec = u = strtoumax (strp, &ebuf, 10); + if (!isspace (*ebuf)) + ERROR ((0, 0, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), lineno, + _("Invalid modification time (seconds)"))); + else if (mtime.tv_sec != u) + ERROR ((0, 0, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), lineno, + _("Modification time (seconds) out of range"))); + strp = ebuf; + + errno = 0; + mtime.tv_nsec = u = strtoumax (strp, &ebuf, 10); + if (!isspace (*ebuf)) + ERROR ((0, 0, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), lineno, + _("Invalid modification time (nanoseconds)"))); + else if (mtime.tv_nsec != u) + ERROR ((0, 0, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), lineno, + _("Modification time (nanoseconds) out of range"))); + strp = ebuf; + } + else + memset (&mtime, 0, sizeof mtime); + errno = 0; - dev = u = strtoul (strp, &ebuf, 10); - if (strp == ebuf || (u == 0 && errno == EINVAL)) + dev = u = strtoumax (strp, &ebuf, 10); + if (!isspace (*ebuf)) ERROR ((0, 0, "%s:%ld: %s", quotearg_colon (listed_incremental_option), lineno, _("Invalid device number"))); - else if (dev != u || (u == -1 && errno == ERANGE)) + else if (dev != u) ERROR ((0, 0, "%s:%ld: %s", quotearg_colon (listed_incremental_option), lineno, _("Device number out of range"))); strp = ebuf; errno = 0; - ino = u = strtoul (strp, &ebuf, 10); - if (strp == ebuf || (u == 0 && errno == EINVAL)) + ino = u = strtoumax (strp, &ebuf, 10); + if (!isspace (*ebuf)) ERROR ((0, 0, "%s:%ld: %s", quotearg_colon (listed_incremental_option), lineno, _("Invalid inode number"))); - else if (ino != u || (u == -1 && errno == ERANGE)) + else if (ino != u) ERROR ((0, 0, "%s:%ld: %s", quotearg_colon (listed_incremental_option), lineno, _("Inode number out of range"))); @@ -397,7 +560,7 @@ read_directory_file (void) strp++; unquote_string (strp); - note_directory (strp, dev, ino, nfs, 0); + note_directory (strp, mtime, dev, ino, nfs, 0); } } @@ -418,11 +581,17 @@ write_directory_file_entry (void *entry, void *data) if (directory->found) { int e; + char buf[UINTMAX_STRSIZE_BOUND]; char *str = quote_copy_string (directory->name); - fprintf (fp, "+%lu %lu %s\n" + ! directory->nfs, - (unsigned long) directory->device_number, - (unsigned long) directory->inode_number, - str ? str : directory->name); + + if (directory->nfs) + fprintf (fp, "+"); + fprintf (fp, "%s ", umaxtostr (directory->mtime.tv_sec, buf)); + fprintf (fp, "%s ", umaxtostr (directory->mtime.tv_nsec, buf)); + fprintf (fp, "%s ", umaxtostr (directory->device_number, buf)); + fprintf (fp, "%s ", umaxtostr (directory->inode_number, buf)); + fprintf (fp, "%s\n", str ? str : directory->name); + e = errno; if (str) free (str); @@ -445,7 +614,12 @@ write_directory_file (void) if (sys_truncate (fileno (fp)) != 0) truncate_error (listed_incremental_option); - fprintf (fp, "%lu\n", (unsigned long) start_time); + fprintf (fp, "%s-%s-%d\n", PACKAGE_NAME, PACKAGE_VERSION, + TAR_INCREMENTAL_VERSION); + + fprintf (fp, "%lu %lu\n", + (unsigned long int) start_time.tv_sec, + (unsigned long int) start_time.tv_nsec); if (! ferror (fp) && directory_table) hash_do_for_each (directory_table, write_directory_file_entry, fp); if (ferror (fp)) @@ -457,44 +631,31 @@ write_directory_file (void) /* Restoration of incremental dumps. */ -/* Examine the directories under directory_name and delete any - files that were not there at the time of the back-up. - FIXME: The function name is obviously a misnomer */ void -gnu_restore (char const *directory_name) +get_gnu_dumpdir () { - char *archive_dir; - char *current_dir; - char *cur, *arc; size_t size; size_t copied; union block *data_block; char *to; - - current_dir = savedir (directory_name); - - if (!current_dir) - { - /* The directory doesn't exist now. It'll be created. In any - case, we don't have to delete any files out of it. */ - - skip_member (); - return; - } - + char *archive_dir; + size = current_stat_info.stat.st_size; if (size != current_stat_info.stat.st_size) xalloc_die (); + archive_dir = xmalloc (size); to = archive_dir; + + set_next_block_after (current_header); + mv_begin (¤t_stat_info); + for (; size > 0; size -= copied) { + mv_size_left (size); data_block = find_next_block (); if (!data_block) - { - ERROR ((0, 0, _("Unexpected EOF in archive"))); - break; /* FIXME: What happens then? */ - } + ERROR ((1, 0, _("Unexpected EOF in archive"))); copied = available_space_after (data_block); if (copied > size) copied = size; @@ -504,9 +665,42 @@ gnu_restore (char const *directory_name) (data_block->buffer + copied - 1)); } + mv_end (); + + current_stat_info.stat.st_size = 0; /* For skip_member() and friends + to work correctly */ + current_stat_info.dumpdir = archive_dir; +} + + +/* Examine the directories under directory_name and delete any + files that were not there at the time of the back-up. */ +void +purge_directory (char const *directory_name) +{ + char *current_dir; + char *cur, *arc; + + if (!current_stat_info.dumpdir) + { + skip_member (); + return; + } + + current_dir = savedir (directory_name); + + if (!current_dir) + { + /* The directory doesn't exist now. It'll be created. In any + case, we don't have to delete any files out of it. */ + + skip_member (); + return; + } + for (cur = current_dir; *cur; cur += strlen (cur) + 1) { - for (arc = archive_dir; *arc; arc += strlen (arc) + 1) + for (arc = current_stat_info.dumpdir; *arc; arc += strlen (arc) + 1) { arc++; if (!strcmp (arc, cur)) @@ -514,13 +708,30 @@ gnu_restore (char const *directory_name) } if (*arc == '\0') { + struct stat st; char *p = new_name (directory_name, cur); + + if (deref_stat (false, p, &st)) + { + stat_diag (p); + WARN((0, 0, _("%s: Not purging directory: unable to stat"), + quotearg_colon (p))); + continue; + } + else if (one_file_system_option && st.st_dev != root_device) + { + WARN((0, 0, + _("%s: directory is on a different device: not purging"), + quotearg_colon (p))); + continue; + } + if (! interactive_option || confirm ("delete", p)) { if (verbose_option) fprintf (stdlis, _("%s: Deleting %s\n"), program_name, quote (p)); - if (! remove_any_file (p, 1)) + if (! remove_any_file (p, RECURSIVE_REMOVE_OPTION)) { int e = errno; ERROR ((0, e, _("%s: Cannot remove"), quotearg_colon (p))); @@ -531,5 +742,33 @@ gnu_restore (char const *directory_name) } free (current_dir); - free (archive_dir); +} + +void +list_dumpdir (char *buffer, size_t size) +{ + while (size) + { + switch (*buffer) + { + case 'Y': + case 'N': + case 'D': + fprintf (stdlis, "%c ", *buffer); + buffer++; + size--; + break; + + case 0: + fputc ('\n', stdlis); + buffer++; + size--; + break; + + default: + fputc (*buffer, stdlis); + buffer++; + size--; + } + } }