X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fincremen.c;h=e4645fa1f16a5b007b3603672f220e9531c34e96;hb=ae00dc0d37baef4504408c9ce03997781b735d41;hp=bde8dc39d77bb71a0369857eba4bcf38d25d13c0;hpb=70b61d565b35d3c97f071177995f55c33d0cfdd0;p=chaz%2Ftar diff --git a/src/incremen.c b/src/incremen.c index bde8dc3..e4645fa 100644 --- a/src/incremen.c +++ b/src/incremen.c @@ -1,5 +1,7 @@ /* GNU dump extensions to tar. - Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc. + + Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001, + 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 @@ -13,680 +15,692 @@ 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 Place - Suite 330, Boston, MA 02111-1307, USA. */ - -#include "system.h" + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -time_t time (); +#include +#include +#include +#include +#include "common.h" -#define ISDIGIT(Char) (ISASCII (Char) && isdigit (Char)) -#define ISSPACE(Char) (ISASCII (Char) && isspace (Char)) +/* Incremental dump specialities. */ -#include "common.h" - -/* Variable sized generic character buffers. */ +/* Which child files to save under a directory. */ +enum children {NO_CHILDREN, CHANGED_CHILDREN, ALL_CHILDREN}; -struct accumulator -{ - int allocated; - int length; - char *pointer; -}; +/* 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]; /* file name of directory */ + }; -/* Amount of space guaranteed just after a reallocation. */ -#define ACCUMULATOR_SLACK 50 +static Hash_table *directory_table; -/*---------------------------------------------------------. -| Return the accumulated data from an ACCUMULATOR buffer. | -`---------------------------------------------------------*/ +#if HAVE_ST_FSTYPE_STRING + static char const nfs_string[] = "nfs"; +# define NFS_FILE_STAT(st) (strcmp ((st).st_fstype, nfs_string) == 0) +#else +# define ST_DEV_MSB(st) (~ (dev_t) 0 << (sizeof (st).st_dev * CHAR_BIT - 1)) +# define NFS_FILE_STAT(st) (((st).st_dev & ST_DEV_MSB (st)) != 0) +#endif -static char * -get_accumulator (struct accumulator *accumulator) +/* Calculate the hash of a directory. */ +static size_t +hash_directory (void const *entry, size_t n_buckets) { - return accumulator->pointer; + struct directory const *directory = entry; + return hash_string (directory->name, n_buckets); } -/*-----------------------------------------------. -| Allocate and return a new accumulator buffer. | -`-----------------------------------------------*/ - -static struct accumulator * -new_accumulator (void) +/* Compare two directories for equality. */ +static bool +compare_directories (void const *entry1, void const *entry2) { - struct accumulator *accumulator - = (struct accumulator *) xmalloc (sizeof (struct accumulator)); - - accumulator->allocated = ACCUMULATOR_SLACK; - accumulator->pointer = (char *) xmalloc (ACCUMULATOR_SLACK); - accumulator->length = 0; - return accumulator; + struct directory const *directory1 = entry1; + struct directory const *directory2 = entry2; + return strcmp (directory1->name, directory2->name) == 0; } -/*-----------------------------------. -| Deallocate an ACCUMULATOR buffer. | -`-----------------------------------*/ - -static void -delete_accumulator (struct accumulator *accumulator) +/* Create and link a new directory entry for directory NAME, having a + device number DEV and an inode number INO, with NFS indicating + 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, struct timespec mtime, + dev_t dev, ino_t ino, bool nfs, bool found) { - free (accumulator->pointer); - free (accumulator); + 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; + directory->nfs = nfs; + directory->found = found; + strcpy (directory->name, name); + + if (! ((directory_table + || (directory_table = hash_initialize (0, 0, hash_directory, + compare_directories, 0))) + && hash_insert (directory_table, directory))) + xalloc_die (); + + return directory; } -/*----------------------------------------------------------------------. -| At the end of an ACCUMULATOR buffer, add a DATA block of SIZE bytes. | -`----------------------------------------------------------------------*/ - -static void -add_to_accumulator (struct accumulator *accumulator, - const char *data, int size) +/* Return a directory entry for a given file NAME, or zero if none found. */ +static struct directory * +find_directory (char *name) { - if (accumulator->length + size > accumulator->allocated) + if (! directory_table) + return 0; + else { - accumulator->allocated = accumulator->length + size + ACCUMULATOR_SLACK; - accumulator->pointer = (char *) - xrealloc (accumulator->pointer, (size_t) accumulator->allocated); + size_t size = offsetof (struct directory, name) + strlen (name) + 1; + struct directory *dir = alloca (size); + strcpy (dir->name, name); + return hash_lookup (directory_table, dir); } - memcpy (accumulator->pointer + accumulator->length, data, (size_t) size); - accumulator->length += size; } - -/* Incremental dump specialities. */ -/* Current time. */ -static time_t time_now; - -/* List of directory names. */ -struct directory - { - struct directory *next; /* next entry in list */ - const char *name; /* path name of directory */ - int device_number; /* device number for directory */ - int inode_number; /* inode number for directory */ - int allnew; - const char *dir_text; - }; -static struct directory *directory_list = NULL; - -/*-------------------------------------------------------------------. -| Create and link a new directory entry for directory NAME, having a | -| DEVICE_NUMBER and a INODE_NUMBER, with some TEXT. | -`-------------------------------------------------------------------*/ - -static void -note_directory (char *name, dev_t device_number, ino_t inode_number, - const char *text) -{ - struct directory *directory - = (struct directory *) xmalloc (sizeof (struct directory)); - - directory->next = directory_list; - directory_list = directory; - - directory->device_number = device_number; - directory->inode_number = inode_number; - directory->name = xstrdup (name); - directory->dir_text = text; - directory->allnew = 0; -} - -/*------------------------------------------------------------------------. -| Return a directory entry for a given path NAME, or NULL if none found. | -`------------------------------------------------------------------------*/ - -static struct directory * -find_directory (char *name) +void +update_parent_directory (const char *name) { struct directory *directory; - - for (directory = directory_list; - directory; - directory = directory->next) + 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) { - if (!strcmp (directory->name, name)) - return directory; + struct stat st; + if (deref_stat (dereference_option, p, &st) != 0) + stat_diag (name); + else + directory->mtime = get_stat_mtime (&st); } - return NULL; + free (p); } -/*---. -| ? | -`---*/ - static int -compare_dirents (const voidstar first, const voidstar second) +compare_dirents (const void *first, const void *second) { return strcmp ((*(char *const *) first) + 1, (*(char *const *) second) + 1); } -/*---. -| ? | -`---*/ - -char * -get_directory_contents (char *path, int device) -{ - struct accumulator *accumulator; - - /* Recursively scan the given PATH. */ - - { - DIR *dirp = opendir (path); /* for scanning directory */ - struct dirent *entry; /* directory entry being scanned */ - char *name_buffer; /* directory, `/', and directory member */ - int name_buffer_size; /* allocated size of name_buffer, minus 2 */ - int name_length; /* used length in name_buffer */ - struct directory *directory; /* for checking if already already seen */ - int all_children; - - if (dirp == NULL) - { - ERROR ((0, errno, _("Cannot open directory %s"), path)); - return NULL; - } - errno = 0; /* FIXME: errno should be read-only */ - - name_buffer_size = strlen (path) + NAME_FIELD_SIZE; - name_buffer = xmalloc ((size_t) (name_buffer_size + 2)); - strcpy (name_buffer, path); - if (path[strlen (path) - 1] != '/') - strcat (name_buffer, "/"); - name_length = strlen (name_buffer); - - directory = find_directory (path); - all_children = directory ? directory->allnew : 0; +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; +} - accumulator = new_accumulator (); - while (entry = readdir (dirp), entry) +/* Recursively scan the given directory. */ +static void +scan_directory (struct obstack *stk, char *dir_name, dev_t device) +{ + 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 */ + enum children children; + struct stat stat_data; + + if (! dirp) + savedir_error (dir_name); + + name_buffer_size = strlen (dir_name) + NAME_FIELD_SIZE; + name_buffer = xmalloc (name_buffer_size + 2); + strcpy (name_buffer, dir_name); + if (! ISSLASH (dir_name[strlen (dir_name) - 1])) + strcat (name_buffer, "/"); + name_length = strlen (name_buffer); + + 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; + (entrylen = strlen (entry)) != 0; + entry += entrylen + 1) { - struct stat stat_data; - - /* Skip `.' and `..'. */ - - if (is_dot_or_dotdot (entry->d_name)) - continue; - - if ((int) NAMLEN (entry) + name_length >= name_buffer_size) + if (name_buffer_size <= entrylen + name_length) { - while ((int) NAMLEN (entry) + name_length >= name_buffer_size) + do name_buffer_size += NAME_FIELD_SIZE; - name_buffer = (char *) - xrealloc (name_buffer, (size_t) (name_buffer_size + 2)); + while (name_buffer_size <= entrylen + name_length); + name_buffer = xrealloc (name_buffer, name_buffer_size + 2); } - strcpy (name_buffer + name_length, entry->d_name); + strcpy (name_buffer + name_length, entry); - if (dereference_option -#ifdef AIX - ? statx (name_buffer, &stat_data, STATSIZE, STX_HIDDEN) - : statx (name_buffer, &stat_data, STATSIZE, STX_HIDDEN | STX_LINK) -#else - ? stat (name_buffer, &stat_data) - : lstat (name_buffer, &stat_data) -#endif - ) + if (excluded_name (name_buffer)) + obstack_1grow (stk, 'N'); + else { - ERROR ((0, errno, _("Cannot stat %s"), name_buffer)); - continue; - } - - if ((one_file_system_option && device != stat_data.st_dev) - || (exclude_option && check_exclude (name_buffer))) - add_to_accumulator (accumulator, "N", 1); -#ifdef AIX - else if (S_ISHIDDEN (stat_data.st_mode)) - { - add_to_accumulator (accumulator, "D", 1); - strcat (entry->d_name, "A"); - entry->d_namlen++; - } -#endif + if (deref_stat (dereference_option, name_buffer, &stat_data)) + { + stat_diag (name_buffer); + continue; + } - else if (S_ISDIR (stat_data.st_mode)) - { - if (directory = find_directory (name_buffer), directory) + if (S_ISDIR (stat_data.st_mode)) { - /* Devices having the high bit set are NFS devices, which are - attributed somewhat randomly in automounting situations. - For avoiding spurious incremental redumping of directories, - we have to plainly consider all NFS devices as equal, - relying on the i-node only to establish differences. */ - - /* FIXME: Göran Uddeborg says, on - 1996-09-20, that SunOS 5/Solaris 2 uses unsigned long for - the device number type. */ - - if ((((short) directory->device_number >= 0 - || (short) stat_data.st_dev >= 0) - && directory->device_number != stat_data.st_dev) - || directory->inode_number != stat_data.st_ino) - { - if (verbose_option) - WARN ((0, 0, _("Directory %s has been renamed"), - name_buffer)); - directory->allnew = 1; - directory->device_number = stat_data.st_dev; - directory->inode_number = stat_data.st_ino; - } - directory->dir_text = ""; + procdir (name_buffer, &stat_data, device, children, + verbose_option); + obstack_1grow (stk, 'D'); } - else + + else if (one_file_system_option && device != stat_data.st_dev) + obstack_1grow (stk, 'N'); + +#ifdef S_ISHIDDEN + else if (S_ISHIDDEN (stat_data.st_mode)) { - if (verbose_option) - WARN ((0, 0, _("Directory %s is new"), name_buffer)); - note_directory (name_buffer, stat_data.st_dev, stat_data.st_ino, - ""); - directory = find_directory (name_buffer); - directory->allnew = 1; + obstack_1grow (stk, 'D'); + obstack_grow (stk, entry, entrylen); + obstack_grow (stk, "A", 2); + continue; } - if (all_children && directory) - directory->allnew = 1; +#endif - add_to_accumulator (accumulator, "D", 1); + else + if (children == CHANGED_CHILDREN + && OLDER_STAT_TIME (stat_data, m) + && (!after_date_option || OLDER_STAT_TIME (stat_data, c))) + obstack_1grow (stk, 'N'); + else + obstack_1grow (stk, 'Y'); } - else - if (!all_children - && stat_data.st_mtime < newer_mtime_option - && (!after_date_option - || stat_data.st_ctime < newer_ctime_option)) - add_to_accumulator (accumulator, "N", 1); - else - add_to_accumulator (accumulator, "Y", 1); - - add_to_accumulator (accumulator, - entry->d_name, (int) (NAMLEN (entry) + 1)); + obstack_grow (stk, entry, entrylen + 1); } - add_to_accumulator (accumulator, "\000\000", 2); - free (name_buffer); - closedir (dirp); - } + obstack_grow (stk, "\000\000", 2); - /* Sort the contents of the directory, now that we have it all. */ + free (name_buffer); + if (dirp) + free (dirp); +} - { - char *pointer = get_accumulator (accumulator); - size_t counter; - char *cursor; - char *buffer; - char **array; - char **array_cursor; - - counter = 0; - for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1) - counter++; - - if (counter == 0) - { - delete_accumulator (accumulator); - return NULL; - } +/* Sort the contents of the obstack, and convert it to the char * */ +static char * +sort_obstack (struct obstack *stk) +{ + char *pointer = obstack_finish (stk); + size_t counter; + char *cursor; + char *buffer; + char **array; + char **array_cursor; - array = (char **) xmalloc (sizeof (char *) * (counter + 1)); + counter = 0; + for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1) + counter++; - array_cursor = array; - for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1) - *array_cursor++ = cursor; - *array_cursor = NULL; + if (!counter) + return NULL; - qsort ((voidstar) array, counter, sizeof (char *), compare_dirents); + array = obstack_alloc (stk, sizeof (char *) * (counter + 1)); - buffer = (char *) xmalloc ((size_t) (cursor - pointer + 2)); + array_cursor = array; + for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1) + *array_cursor++ = cursor; + *array_cursor = 0; - cursor = buffer; - for (array_cursor = array; *array_cursor; array_cursor++) - { - char *string = *array_cursor; + qsort (array, counter, sizeof (char *), compare_dirents); - while ((*cursor++ = *string++)) - continue; - } - *cursor = '\0'; + buffer = xmalloc (cursor - pointer + 2); - delete_accumulator (accumulator); - free (array); - return buffer; - } -} + cursor = buffer; + for (array_cursor = array; *array_cursor; array_cursor++) + { + char *string = *array_cursor; -/*----------------------------------------------------------------------. -| Add all the files in PATH, which is a directory, to the namelist. If | -| any of the files is a directory, recurse on the subdirectory. | -`----------------------------------------------------------------------*/ + while ((*cursor++ = *string++)) + continue; + } + *cursor = '\0'; + return buffer; +} -static void -add_hierarchy_to_namelist (char *path, int device) +char * +get_directory_contents (char *dir_name, dev_t device) { - char *buffer = get_directory_contents (path, device); - - { - struct name *name; + struct obstack stk; + char *buffer; + + obstack_init (&stk); + scan_directory (&stk, dir_name, device); + buffer = sort_obstack (&stk); + obstack_free (&stk, NULL); + return buffer; +} - for (name = namelist; name; name = name->next) - if (strcmp (name->name, path) == 0) - break; - if (name) - name->dir_contents = buffer ? buffer : "\0\0\0\0"; - } +size_t +dumpdir_size (const char *p) +{ + size_t totsize = 0; - if (buffer) + while (*p) { - int name_length = strlen (path); - int allocated_length = (name_length >= NAME_FIELD_SIZE - ? name_length + NAME_FIELD_SIZE - : NAME_FIELD_SIZE); - char *name_buffer = xmalloc ((size_t) (allocated_length + 1)); - /* FIXME: + 2 above? */ - char *string; - int string_length; - - strcpy (name_buffer, path); - if (name_buffer[name_length - 1] != '/') - { - name_buffer[name_length++] = '/'; - name_buffer[name_length] = '\0'; - } - - for (string = buffer; *string; string += string_length + 1) - { - string_length = strlen (string); - if (*string == 'D') - { - if (name_length + string_length >= allocated_length) - { - while (name_length + string_length >= allocated_length) - allocated_length += NAME_FIELD_SIZE; - name_buffer = (char *) - xrealloc (name_buffer, (size_t) (allocated_length + 1)); - } - strcpy (name_buffer + name_length, string + 1); - addname (name_buffer); - add_hierarchy_to_namelist (name_buffer, device); - } - } - - free (name_buffer); + size_t size = strlen (p) + 1; + totsize += size; + p += size; } + return totsize + 1; } + -/*---. -| ? | -`---*/ -static void -read_directory_file (void) -{ - dev_t device_number; - ino_t inode_number; - char *strp; - FILE *fp; - char buf[512]; - static char *path = NULL; +static FILE *listed_incremental_stream; - if (path == NULL) - path = xmalloc (PATH_MAX); - time (&time_now); - if (listed_incremental_option[0] != '/') - { -#if HAVE_GETCWD - if (!getcwd (path, PATH_MAX)) - FATAL_ERROR ((0, 0, _("Could not get current directory"))); -#else - char *getwd (); +/* 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. - if (!getwd (path)) - FATAL_ERROR ((0, 0, _("Could not get current directory: %s"), path)); -#endif + 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 */ - if (strlen (path) + 1 + strlen (listed_incremental_option) + 1 > PATH_MAX) - ERROR ((TAREXIT_FAILURE, 0, _("File name %s/%s too long"), - path, listed_incremental_option)); +#define TAR_INCREMENTAL_VERSION 1 - strcat (path, "/"); - strcat (path, listed_incremental_option); - listed_incremental_option = path; - } - fp = fopen (listed_incremental_option, "r"); - if (fp == 0 && errno != ENOENT) +/* 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) +{ + int fd; + FILE *fp; + char *buf = 0; + size_t bufsize; + + /* Open the file for both read and write. That way, we can write + it later without having to reopen it, and don't have to worry if + we chdir in the meantime. */ + fd = open (listed_incremental_option, O_RDWR | O_CREAT, MODE_RW); + if (fd < 0) { - ERROR ((0, errno, _("Cannot open %s"), listed_incremental_option)); + open_error (listed_incremental_option); return; } - if (!fp) - return; - fgets (buf, sizeof (buf), fp); - - /* FIXME: Using after_date_option as a first time flag looks fairly - dubious to me! So, using -N with incremental might be buggy just - because of the next few lines. I saw a few unexplained, almost harsh - advices, from other GNU people, about *not* using -N with incremental - dumps, and here might lie (part of) the reason. */ - if (!after_date_option) - { - newer_mtime_option = atol (buf); - after_date_option = 1; - } - while (fgets (buf, sizeof (buf), fp)) + fp = fdopen (fd, "r+"); + if (! fp) { - strp = &buf[strlen (buf)]; - if (strp[-1] == '\n') - strp[-1] = '\0'; - /* FIXME: For files ending with an incomplete line, maybe a NUL might - be missing, here... */ - - strp = buf; - device_number = atol (strp); - while (ISDIGIT (*strp)) - strp++; - inode_number = atol (strp); - while (ISSPACE (*strp)) - strp++; - while (ISDIGIT (*strp)) - strp++; - strp++; - unquote_string (strp); - note_directory (strp, device_number, inode_number, NULL); + open_error (listed_incremental_option); + close (fd); + return; } - if (fclose (fp) == EOF) - ERROR ((0, errno, "%s", listed_incremental_option)); -} -/*---. -| ? | -`---*/ + listed_incremental_stream = fp; -void -write_dir_file (void) -{ - FILE *fp; - struct directory *directory; - char *str; - - fp = fopen (listed_incremental_option, "w"); - if (fp == 0) - { - ERROR ((0, errno, _("Cannot write to %s"), listed_incremental_option)); - return; - } - fprintf (fp, "%lu\n", time_now); - for (directory = directory_list; directory; directory = directory->next) + if (0 < getline (&buf, &bufsize, fp)) { - if (!directory->dir_text) - continue; - str = quote_copy_string (directory->name); - if (str) + char *ebuf; + int n; + long lineno = 1; + uintmax_t u; + time_t t = u; + int incremental_version; + + if (strncmp (buf, PACKAGE_NAME, sizeof PACKAGE_NAME - 1) == 0) { - fprintf (fp, "%u %u %s\n", directory->device_number, - directory->inode_number, str); - free (str); + 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 - fprintf (fp, "%u %u %s\n", directory->device_number, - directory->inode_number, directory->name); - } - if (fclose (fp) == EOF) - ERROR ((0, errno, "%s", listed_incremental_option)); -} + 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:%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"))); + 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 + { + /* 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))) + { + dev_t dev; + ino_t ino; + bool nfs = buf[0] == '+'; + char *strp = buf + nfs; + struct timespec mtime; -static int -compare_names (char *param1, char *param2) -{ - struct name *n1 = (struct name *) param1; - struct name *n2 = (struct name *) param2; + lineno++; - if (n1->found) - return n2->found ? strcmp (n1->name, n2->name) : -1; + if (buf[n - 1] == '\n') + buf[n - 1] = '\0'; - if (n2->found) - return 1; + 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 = 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) + ERROR ((0, 0, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), lineno, + _("Device number out of range"))); + strp = ebuf; + + errno = 0; + 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) + ERROR ((0, 0, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), lineno, + _("Inode number out of range"))); + strp = ebuf; + + strp++; + unquote_string (strp); + note_directory (strp, mtime, dev, ino, nfs, 0); + } + } - return strcmp (n1->name, n2->name); + if (ferror (fp)) + read_error (listed_incremental_option); + if (buf) + free (buf); } -/*-------------------------------------------------------------------------. -| Collect all the names from argv[] (or whatever), then expand them into a | -| directory tree, and put all the directories at the beginning. | -`-------------------------------------------------------------------------*/ - -void -collect_and_sort_names (void) +/* Output incremental data for the directory ENTRY to the file DATA. + Return nonzero if successful, preserving errno on write failure. */ +static bool +write_directory_file_entry (void *entry, void *data) { - struct name *name; - struct name *next_name; - int num_names; - struct stat statbuf; - - name_gather (); + struct directory const *directory = entry; + FILE *fp = data; - if (listed_incremental_option) - read_directory_file (); - - if (!namelist) - addname ("."); - - for (name = namelist; name; name = next_name) + if (directory->found) { - next_name = name->next; - if (name->found || name->dir_contents) - continue; - if (name->regexp) /* FIXME: just skip regexps for now */ - continue; - if (name->change_dir) - if (chdir (name->change_dir) < 0) - { - ERROR ((0, errno, _("Cannot chdir to %s"), name->change_dir)); - continue; - } - - if ( -#ifdef AIX - statx (name->name, &statbuf, STATSIZE, STX_HIDDEN | STX_LINK) -#else - lstat (name->name, &statbuf) < 0 -#endif - ) - { - ERROR ((0, errno, _("Cannot stat %s"), name->name)); - continue; - } - if (S_ISDIR (statbuf.st_mode)) - { - name->found = 1; - add_hierarchy_to_namelist (name->name, statbuf.st_dev); - } + int e; + char buf[UINTMAX_STRSIZE_BOUND]; + char *str = quote_copy_string (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); + errno = e; } - num_names = 0; - for (name = namelist; name; name = name->next) - num_names++; - namelist = (struct name *) - merge_sort ((voidstar) namelist, num_names, - (char *) (&(namelist->next)) - (char *) namelist, - compare_names); + return ! ferror (fp); +} - for (name = namelist; name; name = name->next) - name->found = 0; +void +write_directory_file (void) +{ + FILE *fp = listed_incremental_stream; - if (listed_incremental_option) - write_dir_file (); + if (! fp) + return; + + if (fseek (fp, 0L, SEEK_SET) != 0) + seek_error (listed_incremental_option); + if (sys_truncate (fileno (fp)) != 0) + truncate_error (listed_incremental_option); + + 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)) + write_error (listed_incremental_option); + if (fclose (fp) != 0) + close_error (listed_incremental_option); } + /* Restoration of incremental dumps. */ -/*---. -| ? | -`---*/ - void -gnu_restore (int skipcrud) +get_gnu_dumpdir () { - char *current_dir; - char *archive_dir; - struct accumulator *accumulator; - char *p; - DIR *dirp; - struct dirent *d; - char *cur, *arc; - long size, copied; + size_t size; + size_t copied; union block *data_block; char *to; + char *archive_dir; + + size = current_stat_info.stat.st_size; + if (size != current_stat_info.stat.st_size) + xalloc_die (); -#define CURRENT_FILE_NAME (skipcrud + current_file_name) - - dirp = opendir (CURRENT_FILE_NAME); - - if (!dirp) - { - /* 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_file ((long) current_stat.st_size); - return; - } - - accumulator = new_accumulator (); - while (d = readdir (dirp), d) - { - if (is_dot_or_dotdot (d->d_name)) - continue; + archive_dir = xmalloc (size); + to = archive_dir; - add_to_accumulator (accumulator, d->d_name, (int) (NAMLEN (d) + 1)); - } - closedir (dirp); - add_to_accumulator (accumulator, "", 1); + set_next_block_after (current_header); + mv_begin (¤t_stat_info); - current_dir = get_accumulator (accumulator); - archive_dir = (char *) xmalloc ((size_t) current_stat.st_size); - to = archive_dir; - for (size = current_stat.st_size; size > 0; size -= copied) + 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; - memcpy (to, data_block->buffer, (size_t) copied); + memcpy (to, data_block->buffer, copied); to += copied; set_next_block_after ((union block *) (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)) @@ -694,22 +708,67 @@ gnu_restore (int skipcrud) } if (*arc == '\0') { - p = new_name (CURRENT_FILE_NAME, cur); - if (interactive_option && !confirm ("delete", p)) + 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) { - free (p); + WARN((0, 0, + _("%s: directory is on a different device: not purging"), + quotearg_colon (p))); continue; } - if (verbose_option) - fprintf (stdlis, _("%s: Deleting %s\n"), program_name, p); - if (!remove_any_file (p, 1)) - ERROR ((0, errno, _("Error while deleting %s"), p)); + + if (! interactive_option || confirm ("delete", p)) + { + if (verbose_option) + fprintf (stdlis, _("%s: Deleting %s\n"), + program_name, quote (p)); + if (! remove_any_file (p, RECURSIVE_REMOVE_OPTION)) + { + int e = errno; + ERROR ((0, e, _("%s: Cannot remove"), quotearg_colon (p))); + } + } free (p); } } - delete_accumulator (accumulator); - free (archive_dir); + free (current_dir); +} -#undef CURRENT_FILE_NAME +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--; + } + } }