X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fincremen.c;h=7e1e36641e74b71b74d3d2f879adda761b0c24a9;hb=c10830a35ba2e0e86dc10c5ab9bbaf163d4c0b3a;hp=eadd80fb056065e8baa4116d7af33c52c3094f8f;hpb=a85cfd69e39bd6f6823fef6fce437cc1cb68ba5e;p=chaz%2Ftar diff --git a/src/incremen.c b/src/incremen.c index eadd80f..7e1e366 100644 --- a/src/incremen.c +++ b/src/incremen.c @@ -1,11 +1,11 @@ /* GNU dump extensions to tar. Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001, - 2003, 2004, 2005, 2006 Free Software Foundation, Inc. + 2003, 2004, 2005, 2006, 2007, 2008 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 - Free Software Foundation; either version 2, or (at your option) any later + Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but @@ -18,7 +18,6 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include -#include #include #include #include "common.h" @@ -46,26 +45,160 @@ enum children #define DIR_IS_FOUND(d) ((d)->flags & DIRF_FOUND) #define DIR_IS_NEW(d) ((d)->flags & DIRF_NEW) #define DIR_IS_RENAMED(d) ((d)->flags & DIRF_RENAMED) - + #define DIR_SET_FLAG(d,f) (d)->flags |= (f) #define DIR_CLEAR_FLAG(d,f) (d)->flags &= ~(f) +struct dumpdir /* Dump directory listing */ +{ + char *contents; /* Actual contents */ + size_t total; /* Total number of elements */ + size_t elc; /* Number of D/N/Y elements. */ + char **elv; /* Array of D/N/Y elements */ +}; + /* Directory attributes. */ struct directory { + struct directory *next; struct timespec mtime; /* Modification time */ dev_t device_number; /* device number for directory */ ino_t inode_number; /* inode number for directory */ - char *contents; /* Directory contents */ - char *icontents; /* Initial contents if the directory was + struct dumpdir *dump; /* Directory contents */ + struct dumpdir *idump; /* Initial contents if the directory was rescanned */ enum children children; /* What to save under this directory */ unsigned flags; /* See DIRF_ macros above */ struct directory *orig; /* If the directory was renamed, points to the original directory structure */ - char name[1]; /* file name of directory */ + const char *tagfile; /* Tag file, if the directory falls under + exclusion_tag_under */ + char *name; /* file name of directory */ }; +struct dumpdir * +dumpdir_create0 (const char *contents, const char *cmask) +{ + struct dumpdir *dump; + size_t i, total, ctsize, len; + char *p; + const char *q; + + for (i = 0, total = 0, ctsize = 1, q = contents; *q; total++, q += len) + { + len = strlen (q) + 1; + ctsize += len; + if (!cmask || strchr (cmask, *q)) + i++; + } + dump = xmalloc (sizeof (*dump) + ctsize); + dump->contents = (char*)(dump + 1); + memcpy (dump->contents, contents, ctsize); + dump->total = total; + dump->elc = i; + dump->elv = xcalloc (i + 1, sizeof (dump->elv[0])); + + for (i = 0, p = dump->contents; *p; p += strlen (p) + 1) + { + if (!cmask || strchr (cmask, *p)) + dump->elv[i++] = p + 1; + } + dump->elv[i] = NULL; + return dump; +} + +struct dumpdir * +dumpdir_create (const char *contents) +{ + return dumpdir_create0 (contents, "YND"); +} + +void +dumpdir_free (struct dumpdir *dump) +{ + free (dump->elv); + free (dump); +} + +static int +compare_dirnames (const void *first, const void *second) +{ + char const *const *name1 = first; + char const *const *name2 = second; + return strcmp (*name1, *name2); +} + +/* Locate NAME in the dumpdir array DUMP. + Return pointer to the slot in DUMP->contents, or NULL if not found */ +char * +dumpdir_locate (struct dumpdir *dump, const char *name) +{ + char **ptr; + if (!dump) + return NULL; + + ptr = bsearch (&name, dump->elv, dump->elc, sizeof (dump->elv[0]), + compare_dirnames); + return ptr ? *ptr - 1: NULL; +} + +struct dumpdir_iter +{ + struct dumpdir *dump; /* Dumpdir being iterated */ + int all; /* Iterate over all entries, not only D/N/Y */ + size_t next; /* Index of the next element */ +}; + +char * +dumpdir_next (struct dumpdir_iter *itr) +{ + size_t cur = itr->next; + char *ret = NULL; + + if (itr->all) + { + ret = itr->dump->contents + cur; + if (*ret == 0) + return NULL; + itr->next += strlen (ret) + 1; + } + else if (cur < itr->dump->elc) + { + ret = itr->dump->elv[cur] - 1; + itr->next++; + } + + return ret; +} + +char * +dumpdir_first (struct dumpdir *dump, int all, struct dumpdir_iter **pitr) +{ + struct dumpdir_iter *itr = xmalloc (sizeof (*itr)); + itr->dump = dump; + itr->all = all; + itr->next = 0; + *pitr = itr; + return dumpdir_next (itr); +} + +/* Return size in bytes of the dumpdir array P */ +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 struct directory *dirhead, *dirtail; static Hash_table *directory_table; static Hash_table *directory_meta_table; @@ -117,24 +250,78 @@ static struct directory * make_directory (const char *name) { size_t namelen = strlen (name); - size_t size = offsetof (struct directory, name) + namelen + 1; - struct directory *directory = xmalloc (size); - strcpy (directory->name, name); - if (ISSLASH (directory->name[namelen-1])) - directory->name[namelen-1] = 0; + struct directory *directory = xmalloc (sizeof (*directory)); + directory->next = NULL; + directory->dump = directory->idump = NULL; + directory->orig = NULL; directory->flags = false; + if (namelen && ISSLASH (name[namelen - 1])) + namelen--; + directory->name = xmalloc (namelen + 1); + memcpy (directory->name, name, namelen); + directory->name[namelen] = 0; + directory->tagfile = NULL; return directory; } - + +static void +free_directory (struct directory *dir) +{ + free (dir->name); + free (dir); +} + +static struct directory * +attach_directory (const char *name) +{ + struct directory *dir = make_directory (name); + if (dirtail) + dirtail->next = dir; + else + dirhead = dir; + dirtail = dir; + return dir; +} + + +static void +replace_prefix (char **pname, const char *samp, size_t slen, + const char *repl, size_t rlen) +{ + char *name = *pname; + size_t nlen = strlen (name); + if (nlen > slen && memcmp (name, samp, slen) == 0 && ISSLASH (name[slen])) + { + if (rlen > slen) + { + name = xrealloc (name, nlen - slen + rlen + 1); + *pname = name; + } + memmove (name + rlen, name + slen, nlen - slen + 1); + memcpy (name, repl, rlen); + } +} + +void +dirlist_replace_prefix (const char *pref, const char *repl) +{ + struct directory *dp; + size_t pref_len = strlen (pref); + size_t repl_len = strlen (repl); + for (dp = dirhead; dp; dp = dp->next) + replace_prefix (&dp->name, pref, pref_len, repl, repl_len); +} + /* 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, char *contents) + dev_t dev, ino_t ino, bool nfs, bool found, + const char *contents) { - struct directory *directory = make_directory (name); + struct directory *directory = attach_directory (name); directory->mtime = mtime; directory->device_number = dev; @@ -145,14 +332,10 @@ note_directory (char const *name, struct timespec mtime, if (found) DIR_SET_FLAG (directory, DIRF_FOUND); if (contents) - { - size_t size = dumpdir_size (contents); - directory->contents = xmalloc (size); - memcpy (directory->contents, contents, size); - } + directory->dump = dumpdir_create (contents); else - directory->contents = NULL; - + directory->dump = NULL; + if (! ((directory_table || (directory_table = hash_initialize (0, 0, hash_directory_name, @@ -181,7 +364,7 @@ find_directory (const char *name) { struct directory *dir = make_directory (name); struct directory *ret = hash_lookup (directory_table, dir); - free (dir); + free_directory (dir); return ret; } } @@ -200,7 +383,7 @@ find_directory_meta (dev_t dev, ino_t ino) dir->device_number = dev; dir->inode_number = ino; ret = hash_lookup (directory_meta_table, dir); - free (dir); + free_directory (dir); return ret; } } @@ -209,7 +392,7 @@ void update_parent_directory (const char *name) { struct directory *directory; - char *p, *name_buffer; + char *p; p = dir_name (name); directory = find_directory (p); @@ -228,17 +411,17 @@ static struct directory * procdir (char *name_buffer, struct stat *stat_data, dev_t device, enum children children, - bool verbose) + bool verbose, + char *entry) { struct directory *directory; bool nfs = NFS_FILE_STAT (*stat_data); - struct name *np; if ((directory = find_directory (name_buffer)) != NULL) { if (DIR_IS_INITED (directory)) return directory; - + /* 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. @@ -246,7 +429,8 @@ procdir (char *name_buffer, struct stat *stat_data, directories, consider all NFS devices as equal, relying on the i-node to establish differences. */ - if (! (((DIR_IS_NFS (directory) & nfs) + if (! ((!check_device_option + || (DIR_IS_NFS (directory) && nfs) || directory->device_number == stat_data->st_dev) && directory->inode_number == stat_data->st_ino)) { @@ -255,12 +439,16 @@ procdir (char *name_buffer, struct stat *stat_data, stat_data->st_ino); if (d) { - if (verbose_option) - WARN ((0, 0, _("%s: Directory has been renamed from %s"), - quotearg_colon (name_buffer), - quote_n (1, d->name))); - directory->orig = d; - DIR_SET_FLAG (directory, DIRF_RENAMED); + if (strcmp (d->name, name_buffer)) + { + if (verbose_option) + WARN ((0, 0, _("%s: Directory has been renamed from %s"), + quotearg_colon (name_buffer), + quote_n (1, d->name))); + directory->orig = d; + DIR_SET_FLAG (directory, DIRF_RENAMED); + dirlist_replace_prefix (d->name, name_buffer); + } directory->children = CHANGED_CHILDREN; } else @@ -277,7 +465,7 @@ procdir (char *name_buffer, struct stat *stat_data, } else directory->children = CHANGED_CHILDREN; - + DIR_SET_FLAG (directory, DIRF_FOUND); } else @@ -295,12 +483,16 @@ procdir (char *name_buffer, struct stat *stat_data, if (d) { - if (verbose) - WARN ((0, 0, _("%s: Directory has been renamed from %s"), - quotearg_colon (name_buffer), - quote_n (1, d->name))); - directory->orig = d; - DIR_SET_FLAG (directory, DIRF_RENAMED); + if (strcmp (d->name, name_buffer)) + { + if (verbose) + WARN ((0, 0, _("%s: Directory has been renamed from %s"), + quotearg_colon (name_buffer), + quote_n (1, d->name))); + directory->orig = d; + DIR_SET_FLAG (directory, DIRF_RENAMED); + dirlist_replace_prefix (d->name, name_buffer); + } directory->children = CHANGED_CHILDREN; } else @@ -309,7 +501,7 @@ procdir (char *name_buffer, struct stat *stat_data, if (verbose) WARN ((0, 0, _("%s: Directory is new"), quotearg_colon (name_buffer))); - directory->children = + directory->children = (listed_incremental_option || (OLDER_STAT_TIME (*stat_data, m) || (after_date_option @@ -323,60 +515,47 @@ procdir (char *name_buffer, struct stat *stat_data, omit it... */ if (one_file_system_option && device != stat_data->st_dev /* ... except if it was explicitely given in the command line */ - && !((np = name_scan (name_buffer, true)) && np->explicit)) + && !is_individual_file (name_buffer)) directory->children = NO_CHILDREN; - else if (children == ALL_CHILDREN) + else if (children == ALL_CHILDREN) directory->children = ALL_CHILDREN; DIR_SET_FLAG (directory, DIRF_INIT); - - return directory; -} -/* Locate NAME in the dumpdir array DUMP. - Return pointer to the slot in the array, or NULL if not found */ -const char * -dumpdir_locate (const char *dump, const char *name) -{ - if (dump) - while (*dump) + { + const char *tag_file_name; + + switch (check_exclusion_tags (name_buffer, &tag_file_name)) { - /* Ignore 'R' (rename) entries, since they break alphabetical ordering. - They normally do not occur in dumpdirs from the snapshot files, - but this function is also used by purge_directory, which operates - on a dumpdir from the archive, hence the need for this test. */ - if (*dump != 'R') - { - int rc = strcmp (dump + 1, name); - if (rc == 0) - return dump; - if (rc > 1) - break; - } - dump += strlen (dump) + 1; - } - return NULL; -} + case exclusion_tag_all: + /* This warning can be duplicated by code in dump_file0, but only + in case when the topmost directory being archived contains + an exclusion tag. */ + exclusion_tag_warning (name_buffer, tag_file_name, + _("directory not dumped")); + if (entry) + *entry = 'N'; + directory->children = NO_CHILDREN; + break; -/* Return size in bytes of the dumpdir array P */ -size_t -dumpdir_size (const char *p) -{ - size_t totsize = 0; + case exclusion_tag_contents: + exclusion_tag_warning (name_buffer, tag_file_name, + _("contents not dumped")); + directory->children = NO_CHILDREN; + break; - while (*p) - { - size_t size = strlen (p) + 1; - totsize += size; - p += size; - } - return totsize + 1; -} + case exclusion_tag_under: + exclusion_tag_warning (name_buffer, tag_file_name, + _("contents not dumped")); + directory->tagfile = tag_file_name; + break; -static int -compare_dirnames (const void *first, const void *second) -{ - return strcmp (*(const char**)first, *(const char**)second); + case exclusion_tag_none: + break; + } + } + + return directory; } /* Compare dumpdir array from DIRECTORY with directory listing DIR and @@ -384,10 +563,10 @@ compare_dirnames (const void *first, const void *second) DIR must be returned by a previous call to savedir(). - File names in DIRECTORY->contents must be sorted - alphabetically. + File names in DIRECTORY->dump->contents must be sorted + alphabetically. - DIRECTORY->contents is replaced with the created template. Each entry is + DIRECTORY->dump is replaced with the created template. Each entry is prefixed with ' ' if it was present in DUMP and with 'Y' otherwise. */ void @@ -399,15 +578,15 @@ makedumpdir (struct directory *directory, const char *dir) const char *p; char const **array; char *new_dump, *new_dump_ptr; - const char *dump; + struct dumpdir *dump; if (directory->children == ALL_CHILDREN) dump = NULL; else if (DIR_IS_RENAMED (directory)) - dump = directory->orig->icontents ? - directory->orig->icontents : directory->orig->contents; + dump = directory->orig->idump ? + directory->orig->idump : directory->orig->dump; else - dump = directory->contents; + dump = directory->dump; /* Count the size of DIR and the number of elements it contains */ dirsize = 0; @@ -433,9 +612,16 @@ makedumpdir (struct directory *directory, const char *dir) const char *loc = dumpdir_locate (dump, array[i]); if (loc) { - *new_dump_ptr++ = ' '; - dump = loc + strlen (loc) + 1; + if (directory->tagfile) + *new_dump_ptr = strcmp (directory->tagfile, array[i]) == 0 ? + ' ' : 'I'; + else + *new_dump_ptr = ' '; + new_dump_ptr++; } + else if (directory->tagfile) + *new_dump_ptr++ = strcmp (directory->tagfile, array[i]) == 0 ? + ' ' : 'I'; else *new_dump_ptr++ = 'Y'; /* New entry */ @@ -444,36 +630,36 @@ makedumpdir (struct directory *directory, const char *dir) ; } *new_dump_ptr = 0; - directory->icontents = directory->contents; - directory->contents = new_dump; + directory->idump = directory->dump; + directory->dump = dumpdir_create0 (new_dump, NULL); free (array); } /* Recursively scan the given directory. */ -static char * -scan_directory (char *dir_name, dev_t device) +static const char * +scan_directory (char *dir, dev_t device) { - char *dirp = savedir (dir_name); /* for scanning directory */ + char *dirp = savedir (dir); /* for scanning directory */ 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 stat stat_data; struct directory *directory; - + if (! dirp) - savedir_error (dir_name); + savedir_error (dir); - name_buffer_size = strlen (dir_name) + NAME_FIELD_SIZE; + name_buffer_size = strlen (dir) + NAME_FIELD_SIZE; name_buffer = xmalloc (name_buffer_size + 2); - strcpy (name_buffer, dir_name); - if (! ISSLASH (dir_name[strlen (dir_name) - 1])) + strcpy (name_buffer, dir); + if (! ISSLASH (dir[strlen (dir) - 1])) strcat (name_buffer, "/"); name_length = strlen (name_buffer); if (deref_stat (dereference_option, name_buffer, &stat_data)) { stat_diag (name_buffer); - /* FIXME: used to be + /* FIXME: used to be children = CHANGED_CHILDREN; but changed to: */ free (name_buffer); @@ -481,19 +667,22 @@ scan_directory (char *dir_name, dev_t device) return NULL; } - directory = procdir (name_buffer, &stat_data, device, NO_CHILDREN, false); - + directory = procdir (name_buffer, &stat_data, device, NO_CHILDREN, false, + NULL); + if (dirp && directory->children != NO_CHILDREN) { - char *entry; /* directory entry being scanned */ + char *entry; /* directory entry being scanned */ size_t entrylen; /* length of directory entry */ + dumpdir_iter_t itr; makedumpdir (directory, dirp); - for (entry = directory->contents; - (entrylen = strlen (entry)) != 0; - entry += entrylen + 1) + for (entry = dumpdir_first (directory->dump, 1, &itr); + entry; + entry = dumpdir_next (itr)) { + entrylen = strlen (entry); if (name_buffer_size <= entrylen - 1 + name_length) { do @@ -502,8 +691,10 @@ scan_directory (char *dir_name, dev_t device) name_buffer = xrealloc (name_buffer, name_buffer_size + 2); } strcpy (name_buffer + name_length, entry + 1); - - if (excluded_name (name_buffer)) + + if (*entry == 'I') /* Ignored entry */ + *entry = 'N'; + else if (excluded_name (name_buffer)) *entry = 'N'; else { @@ -513,23 +704,23 @@ scan_directory (char *dir_name, dev_t device) *entry = 'N'; continue; } - + if (S_ISDIR (stat_data.st_mode)) { + *entry = 'D'; procdir (name_buffer, &stat_data, device, directory->children, - verbose_option); - *entry = 'D'; + verbose_option, entry); } - + else if (one_file_system_option && device != stat_data.st_dev) *entry = 'N'; else if (*entry == 'Y') /* New entry, skip further checks */; - + /* FIXME: if (S_ISHIDDEN (stat_data.st_mode))?? */ - + else if (OLDER_STAT_TIME (stat_data, m) && (!after_date_option || OLDER_STAT_TIME (stat_data, c))) @@ -538,85 +729,47 @@ scan_directory (char *dir_name, dev_t device) *entry = 'Y'; } } + free (itr); } - + free (name_buffer); if (dirp) free (dirp); - return directory->contents; + return directory->dump ? directory->dump->contents : NULL; } -char * -get_directory_contents (char *dir_name, dev_t device) +const char * +get_directory_contents (char *dir, dev_t device) { - return scan_directory (dir_name, device); + return scan_directory (dir, device); } -static bool -try_pos (char *name, int pos, const char *dumpdir) -{ - int i; - static char namechars[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - - if (pos > 0) - for (i = 0; i < sizeof namechars; i++) - { - name[pos] = namechars[i]; - if (!dumpdir_locate (dumpdir, name) - || try_pos (name, pos-1, dumpdir)) - return true; - } - - return false; -} - -static bool -create_temp_name (char *name, const char *dumpdir) -{ - size_t pos = strlen (name) - 6; - return try_pos (name + pos, 5, dumpdir); -} - -char * -make_tmp_dir_name (const char *name) -{ - char *dirname = dir_name (name); - char *tmp_name = NULL; - struct directory *dir = find_directory (dirname); - - tmp_name = new_name (dirname, "000000"); - if (!create_temp_name (tmp_name, dir ? dir->contents : NULL)) - { - free (tmp_name); - tmp_name = NULL; - } - free (dirname); - return tmp_name; -} - static void obstack_code_rename (struct obstack *stk, char *from, char *to) { + char *s; + + s = from[0] == 0 ? from : + safer_name_suffix (from, false, absolute_names_option); obstack_1grow (stk, 'R'); - obstack_grow (stk, from, strlen (from) + 1); + obstack_grow (stk, s, strlen (s) + 1); + + s = to[0] == 0 ? to: + safer_name_suffix (to, false, absolute_names_option); obstack_1grow (stk, 'T'); - obstack_grow (stk, to, strlen (to) + 1); + obstack_grow (stk, s, strlen (s) + 1); } -static bool -rename_handler (void *data, void *proc_data) +static void +store_rename (struct directory *dir, struct obstack *stk) { - struct directory *dir = data; - struct obstack *stk = proc_data; - if (DIR_IS_RENAMED (dir)) { struct directory *prev, *p; - /* Detect eventual cycles and clear DIRF_RENAMED flag, so this entries + /* Detect eventual cycles and clear DIRF_RENAMED flag, so these entries are ignored when hit by this function next time. If the chain forms a cycle, prev points to the entry DIR is renamed from. In this case it still retains DIRF_RENAMED flag, which will be @@ -637,17 +790,19 @@ rename_handler (void *data, void *proc_data) /* Break the cycle by using a temporary name for one of its elements. - FIXME: Leave the choice of the name to the extractor. */ - temp_name = make_tmp_dir_name (dir->name); - obstack_code_rename (stk, dir->name, temp_name); + First, create a temp name stub entry. */ + temp_name = dir_name (dir->name); + obstack_1grow (stk, 'X'); + obstack_grow (stk, temp_name, strlen (temp_name) + 1); + + obstack_code_rename (stk, dir->name, ""); for (p = dir; p != prev; p = p->orig) obstack_code_rename (stk, p->orig->name, p->name); - obstack_code_rename (stk, temp_name, prev->name); + obstack_code_rename (stk, "", prev->name); } } - return true; } const char * @@ -655,10 +810,11 @@ append_incremental_renames (const char *dump) { struct obstack stk; size_t size; - - if (directory_table == NULL) - return dump; + struct directory *dp; + if (dirhead == NULL) + return dump; + obstack_init (&stk); if (dump) { @@ -667,8 +823,10 @@ append_incremental_renames (const char *dump) } else size = 0; - - hash_do_for_each (directory_table, rename_handler, &stk); + + for (dp = dirhead; dp; dp = dp->next) + store_rename (dp, &stk); + if (obstack_object_size (&stk) != size) { obstack_1grow (&stk, 0); @@ -699,7 +857,8 @@ read_incr_db_01 (int version, const char *initbuf) { int n; uintmax_t u; - time_t t = u; + time_t sec; + long int nsec; char *buf = 0; size_t bufsize; char *ebuf; @@ -720,41 +879,49 @@ read_incr_db_01 (int version, const char *initbuf) buf = strdup (initbuf); bufsize = strlen (buf) + 1; } - - t = u = (errno = 0, strtoumax (buf, &ebuf, 10)); - if (buf == ebuf || (u == 0 && errno == EINVAL)) - ERROR ((0, 0, "%s:%ld: %s", + + sec = TYPE_MINIMUM (time_t); + nsec = -1; + errno = 0; + u = strtoumax (buf, &ebuf, 10); + if (!errno && TYPE_MAXIMUM (time_t) < u) + errno = ERANGE; + if (errno || buf == ebuf) + ERROR ((0, errno, "%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 (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; + sec = u; + + if (version == 1 && *ebuf) + { + char const *buf_ns = ebuf + 1; + errno = 0; + u = strtoumax (buf_ns, &ebuf, 10); + if (!errno && BILLION <= u) + errno = ERANGE; + if (errno || buf_ns == ebuf) + { + ERROR ((0, errno, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), + lineno, + _("Invalid time stamp"))); + sec = TYPE_MINIMUM (time_t); + } + else + nsec = u; + } + else + { + /* pre-1 incremental format does not contain nanoseconds */ + nsec = 0; + } } + newer_mtime_option.tv_sec = sec; + newer_mtime_option.tv_nsec = nsec; + while (0 < (n = getline (&buf, &bufsize, listed_incremental_stream))) { @@ -763,65 +930,79 @@ read_incr_db_01 (int version, const char *initbuf) bool nfs = buf[0] == '+'; char *strp = buf + nfs; struct timespec mtime; - + lineno++; - + if (buf[n - 1] == '\n') buf[n - 1] = '\0'; - + if (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"))); + u = strtoumax (strp, &ebuf, 10); + if (!errno && TYPE_MAXIMUM (time_t) < u) + errno = ERANGE; + if (errno || strp == ebuf || *ebuf != ' ') + { + ERROR ((0, errno, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), lineno, + _("Invalid modification time (seconds)"))); + sec = (time_t) -1; + } + else + sec = u; 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"))); + u = strtoumax (strp, &ebuf, 10); + if (!errno && BILLION <= u) + errno = ERANGE; + if (errno || strp == ebuf || *ebuf != ' ') + { + ERROR ((0, errno, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), lineno, + _("Invalid modification time (nanoseconds)"))); + nsec = -1; + } + else + nsec = u; + mtime.tv_sec = sec; + mtime.tv_nsec = nsec; 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"))); + u = strtoumax (strp, &ebuf, 10); + if (!errno && TYPE_MAXIMUM (dev_t) < u) + errno = ERANGE; + if (errno || strp == ebuf || *ebuf != ' ') + { + ERROR ((0, errno, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), lineno, + _("Invalid device number"))); + dev = (dev_t) -1; + } + else + dev = u; 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"))); + u = strtoumax (strp, &ebuf, 10); + if (!errno && TYPE_MAXIMUM (ino_t) < u) + errno = ERANGE; + if (errno || strp == ebuf || *ebuf != ' ') + { + ERROR ((0, errno, "%s:%ld: %s", + quotearg_colon (listed_incremental_option), lineno, + _("Invalid inode number"))); + ino = (ino_t) -1; + } + else + ino = u; strp = ebuf; - + strp++; unquote_string (strp); note_directory (strp, mtime, dev, ino, nfs, false, NULL); @@ -848,31 +1029,128 @@ read_obstack (FILE *fp, struct obstack *stk, size_t *pcount) } /* Read from file FP a nul-terminated string and convert it to - uintmax_t. Return the resulting value in PVAL. + intmax_t. Return the resulting value in PVAL. Assume '-' has + already been read. + + Throw a fatal error if the string cannot be converted or if the + converted value is less than MIN_VAL. */ + +static void +read_negative_num (FILE *fp, intmax_t min_val, intmax_t *pval) +{ + int c; + size_t i; + char buf[INT_BUFSIZE_BOUND (intmax_t)]; + char *ep; + buf[0] = '-'; + + for (i = 1; ISDIGIT (c = getc (fp)); i++) + { + if (i == sizeof buf - 1) + FATAL_ERROR ((0, 0, _("Field too long while reading snapshot file"))); + buf[i] = c; + } + + if (c < 0) + { + if (ferror (fp)) + FATAL_ERROR ((0, errno, _("Read error in snapshot file"))); + else + FATAL_ERROR ((0, 0, _("Unexpected EOF in snapshot file"))); + } + + buf[i] = 0; + errno = 0; + *pval = strtoimax (buf, &ep, 10); + if (c || errno || *pval < min_val) + FATAL_ERROR ((0, errno, _("Unexpected field value in snapshot file"))); +} + +/* Read from file FP a nul-terminated string and convert it to + uintmax_t. Return the resulting value in PVAL. Assume C has + already been read. + + Throw a fatal error if the string cannot be converted or if the + converted value exceeds MAX_VAL. - Throw fatal error if the string cannot be converted. - Return the last character read or EOF on end of file. */ static int -read_num (FILE *fp, uintmax_t *pval) +read_unsigned_num (int c, FILE *fp, uintmax_t max_val, uintmax_t *pval) { - int c; size_t i; char buf[UINTMAX_STRSIZE_BOUND], *ep; - for (i = 0, c = getc (fp); c != EOF && c != 0; c = getc (fp), i++) + for (i = 0; ISDIGIT (c); i++) { if (i == sizeof buf - 1) FATAL_ERROR ((0, 0, _("Field too long while reading snapshot file"))); buf[i] = c; + c = getc (fp); + } + + if (c < 0) + { + if (ferror (fp)) + FATAL_ERROR ((0, errno, _("Read error in snapshot file"))); + else if (i == 0) + return c; + else + FATAL_ERROR ((0, 0, _("Unexpected EOF in snapshot file"))); } + buf[i] = 0; + errno = 0; *pval = strtoumax (buf, &ep, 10); - if (*ep) - FATAL_ERROR ((0, 0, _("Unexpected field value in snapshot file"))); + if (c || errno || max_val < *pval) + FATAL_ERROR ((0, errno, _("Unexpected field value in snapshot file"))); return c; -} +} + +/* Read from file FP a nul-terminated string and convert it to + uintmax_t. Return the resulting value in PVAL. + + Throw a fatal error if the string cannot be converted or if the + converted value exceeds MAX_VAL. + + Return the last character read or EOF on end of file. */ + +static int +read_num (FILE *fp, uintmax_t max_val, uintmax_t *pval) +{ + return read_unsigned_num (getc (fp), fp, max_val, pval); +} + +/* Read from FP two NUL-terminated strings representing a struct + timespec. Return the resulting value in PVAL. + + Throw a fatal error if the string cannot be converted. */ + +static void +read_timespec (FILE *fp, struct timespec *pval) +{ + int c = getc (fp); + intmax_t i; + uintmax_t u; + + if (c == '-') + { + read_negative_num (fp, TYPE_MINIMUM (time_t), &i); + c = 0; + pval->tv_sec = i; + } + else + { + c = read_unsigned_num (c, fp, TYPE_MAXIMUM (time_t), &u); + pval->tv_sec = u; + } + + if (c || read_num (fp, BILLION - 1, &u)) + FATAL_ERROR ((0, 0, "%s: %s", + quotearg_colon (listed_incremental_option), + _("Unexpected EOF in snapshot file"))); + pval->tv_nsec = u; +} /* Read incremental snapshot format 2 */ static void @@ -880,28 +1158,10 @@ read_incr_db_2 () { uintmax_t u; struct obstack stk; - + obstack_init (&stk); - if (read_num (listed_incremental_stream, &u)) - FATAL_ERROR ((0, 0, "%s: %s", - quotearg_colon (listed_incremental_option), - _("Error reading time stamp"))); - newer_mtime_option.tv_sec = u; - if (newer_mtime_option.tv_sec != u) - FATAL_ERROR ((0, 0, "%s: %s", - quotearg_colon (listed_incremental_option), - _("Time stamp out of range"))); - - if (read_num (listed_incremental_stream, &u)) - FATAL_ERROR ((0, 0, "%s: %s", - quotearg_colon (listed_incremental_option), - _("Error reading time stamp"))); - newer_mtime_option.tv_nsec = u; - if (newer_mtime_option.tv_nsec != u) - FATAL_ERROR ((0, 0, "%s: %s", - quotearg_colon (listed_incremental_option), - _("Time stamp out of range"))); + read_timespec (listed_incremental_stream, &newer_mtime_option); for (;;) { @@ -912,43 +1172,21 @@ read_incr_db_2 () char *name; char *content; size_t s; - - if (read_num (listed_incremental_stream, &u)) + + if (read_num (listed_incremental_stream, 1, &u)) return; /* Normal return */ nfs = u; - - if (read_num (listed_incremental_stream, &u)) - break; - mtime.tv_sec = u; - if (mtime.tv_sec != u) - FATAL_ERROR ((0, 0, "%s: %s", - quotearg_colon (listed_incremental_option), - _("Modification time (seconds) out of range"))); - - if (read_num (listed_incremental_stream, &u)) - break; - mtime.tv_nsec = u; - if (mtime.tv_nsec != u) - FATAL_ERROR ((0, 0, "%s: %s", - quotearg_colon (listed_incremental_option), - _("Modification time (nanoseconds) out of range"))); - if (read_num (listed_incremental_stream, &u)) + read_timespec (listed_incremental_stream, &mtime); + + if (read_num (listed_incremental_stream, TYPE_MAXIMUM (dev_t), &u)) break; dev = u; - if (dev != u) - FATAL_ERROR ((0, 0, "%s: %s", - quotearg_colon (listed_incremental_option), - _("Device number out of range"))); - if (read_num (listed_incremental_stream, &u)) + if (read_num (listed_incremental_stream, TYPE_MAXIMUM (ino_t), &u)) break; ino = u; - if (ino != u) - FATAL_ERROR ((0, 0, "%s: %s", - quotearg_colon (listed_incremental_option), - _("Inode number out of range"))); if (read_obstack (listed_incremental_stream, &stk, &s)) break; @@ -959,16 +1197,16 @@ read_incr_db_2 () ; if (getc (listed_incremental_stream) != 0) FATAL_ERROR ((0, 0, "%s: %s", - quotearg_colon (listed_incremental_option), + quotearg_colon (listed_incremental_option), _("Missing record terminator"))); - + content = obstack_finish (&stk); note_directory (name, mtime, dev, ino, nfs, false, content); obstack_free (&stk, content); } FATAL_ERROR ((0, 0, "%s: %s", - quotearg_colon (listed_incremental_option), - _("Unexpected EOF"))); + quotearg_colon (listed_incremental_option), + _("Unexpected EOF in snapshot file"))); } /* Read incremental snapshot file (directory file). @@ -983,7 +1221,6 @@ read_directory_file (void) int fd; char *buf = 0; size_t bufsize; - long lineno = 1; /* 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 @@ -1006,7 +1243,7 @@ read_directory_file (void) if (0 < getline (&buf, &bufsize, listed_incremental_stream)) { char *ebuf; - int incremental_version; + uintmax_t incremental_version; if (strncmp (buf, PACKAGE_NAME, sizeof PACKAGE_NAME - 1) == 0) { @@ -1017,7 +1254,7 @@ read_directory_file (void) if (!*ebuf) ERROR((1, 0, _("Bad incremental file format"))); - incremental_version = (errno = 0, strtoumax (ebuf+1, &ebuf, 10)); + incremental_version = strtoumax (ebuf + 1, NULL, 10); } else incremental_version = 0; @@ -1032,12 +1269,12 @@ read_directory_file (void) case TAR_INCREMENTAL_VERSION: read_incr_db_2 (); break; - + default: - ERROR ((1, 0, _("Unsupported incremental format version: %d"), + ERROR ((1, 0, _("Unsupported incremental format version: %"PRIuMAX), incremental_version)); } - + } if (ferror (listed_incremental_stream)) @@ -1061,7 +1298,9 @@ write_directory_file_entry (void *entry, void *data) s = DIR_IS_NFS (directory) ? "1" : "0"; fwrite (s, 2, 1, fp); - s = umaxtostr (directory->mtime.tv_sec, buf); + s = (TYPE_SIGNED (time_t) + ? imaxtostr (directory->mtime.tv_sec, buf) + : umaxtostr (directory->mtime.tv_sec, buf)); fwrite (s, strlen (s) + 1, 1, fp); s = umaxtostr (directory->mtime.tv_nsec, buf); fwrite (s, strlen (s) + 1, 1, fp); @@ -1071,14 +1310,16 @@ write_directory_file_entry (void *entry, void *data) fwrite (s, strlen (s) + 1, 1, fp); fwrite (directory->name, strlen (directory->name) + 1, 1, fp); - if (directory->contents) + if (directory->dump) { - char *p; - for (p = directory->contents; *p; p += strlen (p) + 1) - { - if (strchr ("YND", *p)) - fwrite (p, strlen (p) + 1, 1, fp); - } + const char *p; + dumpdir_iter_t itr; + + for (p = dumpdir_first (directory->dump, 0, &itr); + p; + p = dumpdir_next (itr)) + fwrite (p, strlen (p) + 1, 1, fp); + free (itr); } fwrite ("\0\0", 2, 1, fp); } @@ -1104,7 +1345,9 @@ write_directory_file (void) fprintf (fp, "%s-%s-%d\n", PACKAGE_NAME, PACKAGE_VERSION, TAR_INCREMENTAL_VERSION); - s = umaxtostr (start_time.tv_sec, buf); + s = (TYPE_SIGNED (time_t) + ? imaxtostr (start_time.tv_sec, buf) + : umaxtostr (start_time.tv_sec, buf)); fwrite (s, strlen (s) + 1, 1, fp); s = umaxtostr (start_time.tv_nsec, buf); fwrite (s, strlen (s) + 1, 1, fp); @@ -1171,52 +1414,177 @@ is_dumpdir (struct tar_stat_info *stat_info) return stat_info->is_dumpdir; } +static bool +dumpdir_ok (char *dumpdir) +{ + char *p; + int has_tempdir = 0; + int expect = 0; + + for (p = dumpdir; *p; p += strlen (p) + 1) + { + if (expect && *p != expect) + { + ERROR ((0, 0, + _("Malformed dumpdir: expected '%c' but found %#3o"), + expect, *p)); + return false; + } + switch (*p) + { + case 'X': + if (has_tempdir) + { + ERROR ((0, 0, + _("Malformed dumpdir: 'X' duplicated"))); + return false; + } + else + has_tempdir = 1; + break; + + case 'R': + if (p[1] == 0) + { + if (!has_tempdir) + { + ERROR ((0, 0, + _("Malformed dumpdir: empty name in 'R'"))); + return false; + } + else + has_tempdir = 0; + } + expect = 'T'; + break; + + case 'T': + if (expect != 'T') + { + ERROR ((0, 0, + _("Malformed dumpdir: 'T' not preceeded by 'R'"))); + return false; + } + if (p[1] == 0 && !has_tempdir) + { + ERROR ((0, 0, + _("Malformed dumpdir: empty name in 'T'"))); + return false; + } + expect = 0; + break; + + case 'N': + case 'Y': + case 'D': + break; + + default: + /* FIXME: bail out? */ + break; + } + } + + if (expect) + { + ERROR ((0, 0, + _("Malformed dumpdir: expected '%c' but found end of data"), + expect)); + return false; + } + + if (has_tempdir) + WARN ((0, 0, _("Malformed dumpdir: 'X' never used"))); + + return true; +} + /* 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) +static bool +try_purge_directory (char const *directory_name) { char *current_dir; char *cur, *arc, *p; + char *temp_stub = NULL; + struct dumpdir *dump; if (!is_dumpdir (¤t_stat_info)) - { - skip_member (); - return; - } + return false; 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. */ + /* The directory doesn't exist now. It'll be created. In any + case, we don't have to delete any files out of it. */ + return false; - skip_member (); - return; - } + /* Verify if dump directory is sane */ + if (!dumpdir_ok (current_stat_info.dumpdir)) + return false; /* Process renames */ for (arc = current_stat_info.dumpdir; *arc; arc += strlen (arc) + 1) { - if (*arc == 'R') + if (*arc == 'X') + { +#define TEMP_DIR_TEMPLATE "tar.XXXXXX" + size_t len = strlen (arc + 1); + temp_stub = xrealloc (temp_stub, len + 1 + sizeof TEMP_DIR_TEMPLATE); + memcpy (temp_stub, arc + 1, len); + temp_stub[len] = '/'; + memcpy (temp_stub + len + 1, TEMP_DIR_TEMPLATE, + sizeof TEMP_DIR_TEMPLATE); + if (!mkdtemp (temp_stub)) + { + ERROR ((0, errno, + _("Cannot create temporary directory using template %s"), + quote (temp_stub))); + free (temp_stub); + free (current_dir); + return false; + } + } + else if (*arc == 'R') { char *src, *dst; src = arc + 1; arc += strlen (arc) + 1; dst = arc + 1; + /* Ensure that neither source nor destination are absolute file + names (unless permitted by -P option), and that they do not + contain dubious parts (e.g. ../). + + This is an extra safety precaution. Besides, it might be + necessary to extract from archives created with tar versions + prior to 1.19. */ + + if (*src) + src = safer_name_suffix (src, false, absolute_names_option); + if (*dst) + dst = safer_name_suffix (dst, false, absolute_names_option); + + if (*src == 0) + src = temp_stub; + else if (*dst == 0) + dst = temp_stub; + if (!rename_directory (src, dst)) { + free (temp_stub); free (current_dir); /* FIXME: Make sure purge_directory(dst) will return immediately */ - return; + return false; } } } - + + free (temp_stub); + /* Process deletes */ + dump = dumpdir_create (current_stat_info.dumpdir); p = NULL; for (cur = current_dir; *cur; cur += strlen (cur) + 1) { @@ -1226,22 +1594,23 @@ purge_directory (char const *directory_name) free (p); p = new_name (directory_name, cur); - if (!(entry = dumpdir_locate (current_stat_info.dumpdir, cur)) - || (*entry == 'D' && S_ISDIR (st.st_mode)) - || (*entry == 'Y' && !S_ISDIR (st.st_mode))) + if (deref_stat (false, p, &st)) { - if (deref_stat (false, p, &st)) + if (errno != ENOENT) /* FIXME: Maybe keep a list of renamed + dirs and check it here? */ { - if (errno != ENOENT) /* FIXME: Maybe keep a list of renamed - dirs and check it here? */ - { - stat_diag (p); - WARN ((0, 0, _("%s: Not purging directory: unable to stat"), - quotearg_colon (p))); - } - continue; + stat_diag (p); + WARN ((0, 0, _("%s: Not purging directory: unable to stat"), + quotearg_colon (p))); } - else if (one_file_system_option && st.st_dev != root_device) + continue; + } + + if (!(entry = dumpdir_locate (dump, cur)) + || (*entry == 'D' && !S_ISDIR (st.st_mode)) + || (*entry == 'Y' && S_ISDIR (st.st_mode))) + { + if (one_file_system_option && st.st_dev != root_device) { WARN ((0, 0, _("%s: directory is on a different device: not purging"), @@ -1263,13 +1632,23 @@ purge_directory (char const *directory_name) } } free (p); + dumpdir_free (dump); free (current_dir); + return true; +} + +void +purge_directory (char const *directory_name) +{ + if (!try_purge_directory (directory_name)) + skip_member (); } void list_dumpdir (char *buffer, size_t size) { + int state = 0; while (size) { switch (*buffer) @@ -1279,7 +1658,13 @@ list_dumpdir (char *buffer, size_t size) case 'D': case 'R': case 'T': - fprintf (stdlis, "%c ", *buffer); + case 'X': + fprintf (stdlis, "%c", *buffer); + if (state == 0) + { + fprintf (stdlis, " "); + state = 1; + } buffer++; size--; break; @@ -1288,6 +1673,7 @@ list_dumpdir (char *buffer, size_t size) fputc ('\n', stdlis); buffer++; size--; + state = 0; break; default: