X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fcreate.c;h=072732a6195250bd1a9ff436971de709a590bf6f;hb=1bcbbcf1ff2c537ffa970dbf82e3843d4ad110e5;hp=e1af8db8a9857915bad15ae00da77bc8da70029b;hpb=273810a9f73a71bc8417a09a9a9c4c569df0fa11;p=chaz%2Ftar diff --git a/src/create.c b/src/create.c index e1af8db..072732a 100644 --- a/src/create.c +++ b/src/create.c @@ -1,13 +1,13 @@ /* Create a tar archive. Copyright (C) 1985, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001, - 2003, 2004, 2005 Free Software Foundation, Inc. + 2003, 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc. Written by John Gilmore, on 1985-08-25. 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 @@ -33,6 +33,112 @@ struct link size_t nlink; char name[1]; }; + +struct exclusion_tag +{ + const char *name; + size_t length; + enum exclusion_tag_type type; + bool (*predicate) (const char *name); + struct exclusion_tag *next; +}; + +static struct exclusion_tag *exclusion_tags; + +void +add_exclusion_tag (const char *name, enum exclusion_tag_type type, + bool (*predicate) (const char *name)) +{ + struct exclusion_tag *tag = xmalloc (sizeof tag[0]); + tag->next = exclusion_tags; + tag->name = name; + tag->type = type; + tag->predicate = predicate; + tag->length = strlen (name); + exclusion_tags = tag; +} + +void +exclusion_tag_warning (const char *dirname, const char *tagname, + const char *message) +{ + if (verbose_option) + WARNOPT (WARN_CACHEDIR, + (0, 0, + _("%s: contains a cache directory tag %s; %s"), + quotearg_colon (dirname), + quotearg_n (1, tagname), + message)); +} + +enum exclusion_tag_type +check_exclusion_tags (const char *dirname, const char **tag_file_name) +{ + static char *tagname; + static size_t tagsize; + struct exclusion_tag *tag; + size_t dlen = strlen (dirname); + int addslash = !ISSLASH (dirname[dlen-1]); + char *nptr = NULL; + + for (tag = exclusion_tags; tag; tag = tag->next) + { + size_t size = dlen + addslash + tag->length + 1; + if (size > tagsize) + { + tagsize = size; + tagname = xrealloc (tagname, tagsize); + } + + if (!nptr) + { + strcpy (tagname, dirname); + nptr = tagname + dlen; + if (addslash) + *nptr++ = '/'; + } + strcpy (nptr, tag->name); + if (access (tagname, F_OK) == 0 + && (!tag->predicate || tag->predicate (tagname))) + { + if (tag_file_name) + *tag_file_name = tag->name; + return tag->type; + } + } + + return exclusion_tag_none; +} + +/* Exclusion predicate to test if the named file (usually "CACHEDIR.TAG") + contains a valid header, as described at: + http://www.brynosaurus.com/cachedir + Applications can write this file into directories they create + for use as caches containing purely regenerable, non-precious data, + allowing us to avoid archiving them if --exclude-caches is specified. */ + +#define CACHEDIR_SIGNATURE "Signature: 8a477f597d28d172789f06886806bc55" +#define CACHEDIR_SIGNATURE_SIZE (sizeof CACHEDIR_SIGNATURE - 1) + +bool +cachedir_file_p (const char *name) +{ + bool tag_present = false; + int fd = open (name, O_RDONLY); + if (fd >= 0) + { + static char tagbuf[CACHEDIR_SIGNATURE_SIZE]; + + if (read (fd, tagbuf, CACHEDIR_SIGNATURE_SIZE) + == CACHEDIR_SIGNATURE_SIZE + && memcmp (tagbuf, CACHEDIR_SIGNATURE, CACHEDIR_SIGNATURE_SIZE) == 0) + tag_present = true; + + close (fd); + } + return tag_present; +} + /* The maximum uintmax_t value that can be represented with DIGITS digits, assuming that each digit is BITS_PER_DIGIT wide. */ @@ -128,7 +234,7 @@ to_chars_subst (int negative, int gnu_format, uintmax_t value, size_t valsize, char const *minval_string; char const *maxval_string = STRINGIFY_BIGINT (maxval, maxbuf); char const *value_string; - + if (gnu_format) { uintmax_t m = maxval + 1 ? maxval + 1 : maxval / 2 + 1; @@ -138,7 +244,7 @@ to_chars_subst (int negative, int gnu_format, uintmax_t value, size_t valsize, } else minval_string = "0"; - + if (negative) { char *p = STRINGIFY_BIGINT (- value, valbuf + 1); @@ -147,13 +253,18 @@ to_chars_subst (int negative, int gnu_format, uintmax_t value, size_t valsize, } else value_string = STRINGIFY_BIGINT (value, valbuf); - + if (substitute) { int negsub; uintmax_t sub = substitute (&negsub) & maxval; - /* FIXME: This is the only place where GNU_FORMAT differs from - OLDGNU_FORMAT. Apart from this they are completely identical. */ + /* NOTE: This is one of the few places where GNU_FORMAT differs from + OLDGNU_FORMAT. The actual differences are: + + 1. In OLDGNU_FORMAT all strings in a tar header end in \0 + 2. Incremental archives use oldgnu_header. + + Apart from this they are completely identical. */ uintmax_t s = (negsub &= archive_format == GNU_FORMAT) ? - sub : sub; char subbuf[UINTMAX_STRSIZE_BOUND + 1]; char *sub_string = STRINGIFY_BIGINT (s, subbuf + 1); @@ -236,7 +347,7 @@ to_chars (int negative, uintmax_t value, size_t valsize, } else substitute = NULL; /* No substitution for formats, other than GNU */ - + return to_chars_subst (negative, gnu_format, value, valsize, substitute, where, size, type); } @@ -291,7 +402,8 @@ mode_to_chars (mode_t v, char *p, size_t s) && S_IROTH == TOREAD && S_IWOTH == TOWRITE && S_IXOTH == TOEXEC && archive_format != POSIX_FORMAT && archive_format != USTAR_FORMAT - && archive_format != GNU_FORMAT) + && archive_format != GNU_FORMAT + && archive_format != OLDGNU_FORMAT) { negative = v < 0; u = v; @@ -380,7 +492,7 @@ bool file_dumpable_p (struct tar_stat_info *st) { if (dev_null_output) - return totals_option && sparse_option && sparse_file_p (st); + return totals_option && sparse_option && ST_IS_SPARSE (st->stat); return !(st->archive_file_size == 0 && (st->stat.st_mode & MODE_R) == MODE_R); } @@ -494,7 +606,7 @@ split_long_name (const char *name, size_t length) size_t i; if (length > PREFIX_FIELD_SIZE) - length = PREFIX_FIELD_SIZE+2; + length = PREFIX_FIELD_SIZE + 1; for (i = length - 1; i > 0; i--) if (ISSLASH (name[i])) break; @@ -602,10 +714,10 @@ write_extended (bool global, struct tar_stat_info *st, union block *old_header) char *p; int type; - if (extended_header.buffer || extended_header.stk == NULL) + if (st->xhdr.buffer || st->xhdr.stk == NULL) return old_header; - xheader_finish (&extended_header); + xheader_finish (&st->xhdr); memcpy (hp.buffer, old_header, sizeof (hp)); if (global) { @@ -617,7 +729,7 @@ write_extended (bool global, struct tar_stat_info *st, union block *old_header) type = XHDTYPE; p = xheader_xhdr_name (st); } - xheader_write (type, p, &extended_header); + xheader_write (type, p, &st->xhdr); free (p); header = find_next_block (); memcpy (header, &hp.buffer, sizeof (hp.buffer)); @@ -663,7 +775,8 @@ start_header (struct tar_stat_info *st) if (mode_option) st->stat.st_mode = ((st->stat.st_mode & ~MODE_ALL) - | mode_adjust (st->stat.st_mode, mode_option, initial_umask)); + | mode_adjust (st->stat.st_mode, S_ISDIR (st->stat.st_mode) != 0, + initial_umask, mode_option, NULL)); /* Paul Eggert tried the trivial test ($WRITER cf a b; $READER tvf a) for a few tars and came up with the following interoperability @@ -730,12 +843,12 @@ start_header (struct tar_stat_info *st) } { - struct timespec mtime = st->mtime; + struct timespec mtime = set_mtime_option ? mtime_option : st->mtime; if (archive_format == POSIX_FORMAT) { if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec || mtime.tv_nsec != 0) - xheader_store ("mtime", st, NULL); + xheader_store ("mtime", st, &mtime); if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec) mtime.tv_sec = 0; } @@ -929,7 +1042,7 @@ dump_regular_file (int fd, struct tar_stat_info *st) while (size_left > 0) { size_t bufsize, count; - + mv_size_left (size_left); blk = find_next_block (); @@ -954,75 +1067,36 @@ dump_regular_file (int fd, struct tar_stat_info *st) return dump_status_short; } size_left -= count; - if (count) - set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE); + set_next_block_after (blk + (bufsize - 1) / BLOCKSIZE); if (count != bufsize) { char buf[UINTMAX_STRSIZE_BOUND]; memset (blk->buffer + count, 0, bufsize - count); - WARN ((0, 0, - ngettext ("%s: File shrank by %s byte; padding with zeros", - "%s: File shrank by %s bytes; padding with zeros", - size_left), - quotearg_colon (st->orig_file_name), - STRINGIFY_BIGINT (size_left, buf))); - if (! ignore_failed_read_option) - exit_status = TAREXIT_FAILURE; - pad_archive (size_left - (bufsize-count)); + WARNOPT (WARN_FILE_SHRANK, + (0, 0, + ngettext ("%s: File shrank by %s byte; padding with zeros", + "%s: File shrank by %s bytes; padding with zeros", + size_left), + quotearg_colon (st->orig_file_name), + STRINGIFY_BIGINT (size_left, buf))); + if (! ignore_failed_read_option) + exit_status = TAREXIT_DIFFERS; + pad_archive (size_left - (bufsize - count)); return dump_status_short; } } return dump_status_ok; } -/* Look in directory DIRNAME for a cache directory tag file - with the magic name "CACHEDIR.TAG" and a standard header, - as described at: - http://www.brynosaurus.com/cachedir - Applications can write this file into directories they create - for use as caches containing purely regenerable, non-precious data, - allowing us to avoid archiving them if --exclude-caches is specified. */ - -#define CACHEDIR_SIGNATURE "Signature: 8a477f597d28d172789f06886806bc55" -#define CACHEDIR_SIGNATURE_SIZE (sizeof CACHEDIR_SIGNATURE - 1) - -static bool -check_cache_directory (char *dirname) -{ - static char tagname[] = "CACHEDIR.TAG"; - char *tagpath; - int fd; - int tag_present = false; - - tagpath = xmalloc (strlen (dirname) + strlen (tagname) + 1); - strcpy (tagpath, dirname); - strcat (tagpath, tagname); - - fd = open (tagpath, O_RDONLY); - if (fd >= 0) - { - static char tagbuf[CACHEDIR_SIGNATURE_SIZE]; - - if (read (fd, tagbuf, CACHEDIR_SIGNATURE_SIZE) - == CACHEDIR_SIGNATURE_SIZE - && memcmp (tagbuf, CACHEDIR_SIGNATURE, CACHEDIR_SIGNATURE_SIZE) == 0) - tag_present = true; - - close (fd); - } - - free (tagpath); - - return tag_present; -} - + static void dump_dir0 (char *directory, struct tar_stat_info *st, int top_level, dev_t parent_device) { dev_t our_device = st->stat.st_dev; - + const char *tag_file_name; + if (!is_avoided_name (st->orig_file_name)) { union block *blk = NULL; @@ -1101,47 +1175,67 @@ dump_dir0 (char *directory, && parent_device != st->stat.st_dev) { if (verbose_option) - WARN ((0, 0, - _("%s: file is on a different filesystem; not dumped"), - quotearg_colon (st->orig_file_name))); - return; + WARNOPT (WARN_XDEV, + (0, 0, + _("%s: file is on a different filesystem; not dumped"), + quotearg_colon (st->orig_file_name))); } - - if (exclude_caches_option - && check_cache_directory(st->orig_file_name)) + else { - if (verbose_option) - WARN ((0, 0, - _("%s: contains a cache directory tag; not dumped"), - quotearg_colon (st->orig_file_name))); - return; - } - - { - char const *entry; - size_t entry_len; - char *name_buf = xstrdup (st->orig_file_name); - size_t name_size = strlen (name_buf); - size_t name_len = name_size; + char *name_buf; + size_t name_size; + + switch (check_exclusion_tags (st->orig_file_name, &tag_file_name)) + { + case exclusion_tag_all: + /* Handled in dump_file0 */ + break; + + case exclusion_tag_none: + { + char const *entry; + size_t entry_len; + size_t name_len; - /* Now output all the files in the directory. */ - /* FIXME: Should speed this up by cd-ing into the dir. */ + name_buf = xstrdup (st->orig_file_name); + name_size = name_len = strlen (name_buf); - for (entry = directory; (entry_len = strlen (entry)) != 0; - entry += entry_len + 1) - { - if (name_size < name_len + entry_len) - { - name_size = name_len + entry_len; - name_buf = xrealloc (name_buf, name_size + 1); + /* Now output all the files in the directory. */ + /* FIXME: Should speed this up by cd-ing into the dir. */ + for (entry = directory; (entry_len = strlen (entry)) != 0; + entry += entry_len + 1) + { + if (name_size < name_len + entry_len) + { + name_size = name_len + entry_len; + name_buf = xrealloc (name_buf, name_size + 1); + } + strcpy (name_buf + name_len, entry); + if (!excluded_name (name_buf)) + dump_file (name_buf, 0, our_device); + } + + free (name_buf); } - strcpy (name_buf + name_len, entry); - if (!excluded_name (name_buf)) + break; + + case exclusion_tag_contents: + exclusion_tag_warning (st->orig_file_name, tag_file_name, + _("contents not dumped")); + name_size = strlen (st->orig_file_name) + strlen (tag_file_name) + 1; + name_buf = xmalloc (name_size); + strcpy (name_buf, st->orig_file_name); + strcat (name_buf, tag_file_name); dump_file (name_buf, 0, our_device); - } - - free (name_buf); - } + free (name_buf); + break; + + case exclusion_tag_under: + exclusion_tag_warning (st->orig_file_name, tag_file_name, + _("contents not dumped")); + break; + } + } } /* Ensure exactly one trailing slash. */ @@ -1167,9 +1261,6 @@ dump_dir (int fd, struct tar_stat_info *st, int top_level, dev_t parent_device) return false; } - ensure_slash (&st->orig_file_name); - ensure_slash (&st->file_name); - dump_dir0 (directory, st, top_level, parent_device); free (directory); @@ -1182,10 +1273,10 @@ dump_dir (int fd, struct tar_stat_info *st, int top_level, dev_t parent_device) void create_archive (void) { - char *p; + const char *p; open_archive (ACCESS_WRITE); - xheader_write_global (); + buffer_write_global_xheader (); if (incremental_option) { @@ -1212,7 +1303,7 @@ create_archive (void) } memcpy (buffer, p, plen); if (! ISSLASH (buffer[plen - 1])) - buffer[plen++] = '/'; + buffer[plen++] = DIRECTORY_SEPARATOR; q = gnu_list_name->dir_contents; if (q) while (*q) @@ -1270,8 +1361,9 @@ compare_links (void const *entry1, void const *entry2) static void unknown_file_error (char const *p) { - WARN ((0, 0, _("%s: Unknown file type; file ignored"), - quotearg_colon (p))); + WARNOPT (WARN_FILE_IGNORED, + (0, 0, _("%s: Unknown file type; file ignored"), + quotearg_colon (p))); if (!ignore_failed_read_option) exit_status = TAREXIT_FAILURE; } @@ -1289,7 +1381,7 @@ static Hash_table *link_table; static bool dump_hard_link (struct tar_stat_info *st) { - if (link_table && st->stat.st_nlink > 1) + if (link_table && (st->stat.st_nlink > 1 || remove_files_option)) { struct link lp; struct link *duplicate; @@ -1334,22 +1426,31 @@ dump_hard_link (struct tar_stat_info *st) static void file_count_links (struct tar_stat_info *st) { + if (hard_dereference_option) + return; if (st->stat.st_nlink > 1) { struct link *duplicate; - struct link *lp = xmalloc (offsetof (struct link, name) - + strlen (st->orig_file_name) + 1); + char *linkname = NULL; + struct link *lp; + + assign_string (&linkname, st->orig_file_name); + transform_name (&linkname, XFORM_LINK); + + lp = xmalloc (offsetof (struct link, name) + + strlen (linkname) + 1); lp->ino = st->stat.st_ino; lp->dev = st->stat.st_dev; lp->nlink = st->stat.st_nlink; - strcpy (lp->name, st->orig_file_name); - + strcpy (lp->name, linkname); + free (linkname); + if (! ((link_table || (link_table = hash_initialize (0, 0, hash_link, compare_links, 0))) && (duplicate = hash_insert (link_table, lp)))) xalloc_die (); - + if (duplicate != lp) abort (); lp->nlink--; @@ -1371,7 +1472,7 @@ check_links (void) { if (lp->nlink) { - WARN ((0, 0, _("Missing links to %s.\n"), quote (lp->name))); + WARN ((0, 0, _("Missing links to %s."), quote (lp->name))); } } } @@ -1387,11 +1488,12 @@ check_links (void) exit_status to failure, a clear diagnostic has been issued. */ static void -dump_file0 (struct tar_stat_info *st, char const *p, +dump_file0 (struct tar_stat_info *st, const char *p, int top_level, dev_t parent_device) { union block *header; char type; + off_t original_size; struct timespec original_ctime; struct timespec restore_times[2]; off_t block_ordinal = -1; @@ -1404,12 +1506,14 @@ dump_file0 (struct tar_stat_info *st, char const *p, assign_string (&st->file_name, safer_name_suffix (p, false, absolute_names_option)); + transform_name (&st->file_name, XFORM_REGFILE); + if (deref_stat (dereference_option, p, &st->stat) != 0) { stat_diag (p); return; } - st->archive_file_size = st->stat.st_size; + st->archive_file_size = original_size = st->stat.st_size; st->atime = restore_times[0] = get_stat_atime (&st->stat); st->mtime = restore_times[1] = get_stat_mtime (&st->stat); st->ctime = original_ctime = get_stat_ctime (&st->stat); @@ -1429,26 +1533,28 @@ dump_file0 (struct tar_stat_info *st, char const *p, /* See if we want only new files, and check if this one is too old to put in the archive. - + This check is omitted if incremental_option is set *and* the requested file is not explicitely listed in the command line. */ - + if (!(incremental_option && !is_individual_file (p)) && !S_ISDIR (st->stat.st_mode) && OLDER_TAR_STAT_TIME (*st, m) && (!after_date_option || OLDER_TAR_STAT_TIME (*st, c))) { if (!incremental_option && verbose_option) - WARN ((0, 0, _("%s: file is unchanged; not dumped"), - quotearg_colon (p))); + WARNOPT (WARN_FILE_UNCHANGED, + (0, 0, _("%s: file is unchanged; not dumped"), + quotearg_colon (p))); return; } /* See if we are trying to dump the archive. */ if (sys_file_is_archive (st)) { - WARN ((0, 0, _("%s: file is the archive; not dumped"), - quotearg_colon (p))); + WARNOPT (WARN_IGNORE_ARCHIVE, + (0, 0, _("%s: file is the archive; not dumped"), + quotearg_colon (p))); return; } @@ -1477,8 +1583,9 @@ dump_file0 (struct tar_stat_info *st, char const *p, if (fd < 0) { if (!top_level && errno == ENOENT) - WARN ((0, 0, _("%s: File removed before we read it"), - quotearg_colon (p))); + WARNOPT (WARN_FILE_REMOVED, + (0, 0, _("%s: File removed before we read it"), + quotearg_colon (p))); else open_diag (p); return; @@ -1487,6 +1594,18 @@ dump_file0 (struct tar_stat_info *st, char const *p, if (is_dir) { + const char *tag_file_name; + ensure_slash (&st->orig_file_name); + ensure_slash (&st->file_name); + + if (check_exclusion_tags (st->orig_file_name, &tag_file_name) + == exclusion_tag_all) + { + exclusion_tag_warning (st->orig_file_name, tag_file_name, + _("directory not dumped")); + return; + } + ok = dump_dir (fd, st, top_level, parent_device); /* dump_dir consumes FD if successful. */ @@ -1497,7 +1616,7 @@ dump_file0 (struct tar_stat_info *st, char const *p, { enum dump_status status; - if (fd != -1 && sparse_option && sparse_file_p (st)) + if (fd != -1 && sparse_option && ST_IS_SPARSE (st->stat)) { status = sparse_dump_file (fd, st); if (status == dump_status_not_implemented) @@ -1511,6 +1630,7 @@ dump_file0 (struct tar_stat_info *st, char const *p, case dump_status_ok: case dump_status_short: mv_end (); + file_count_links (st); break; case dump_status_fail: @@ -1544,9 +1664,18 @@ dump_file0 (struct tar_stat_info *st, char const *p, if (ok) { - if (timespec_cmp (get_stat_ctime (&final_stat), original_ctime) != 0) - WARN ((0, 0, _("%s: file changed as we read it"), - quotearg_colon (p))); + if ((timespec_cmp (get_stat_ctime (&final_stat), original_ctime) != 0 + /* Original ctime will change if the file is a directory and + --remove-files is given */ + && !(remove_files_option && is_dir)) + || original_size < final_stat.st_size) + { + WARNOPT (WARN_FILE_CHANGED, + (0, 0, _("%s: file changed as we read it"), + quotearg_colon (p))); + if (exit_status == TAREXIT_SUCCESS) + exit_status = TAREXIT_DIFFERS; + } else if (atime_preserve_option == replace_atime_preserve && set_file_atime (fd, p, restore_times) != 0) utime_error (p); @@ -1591,6 +1720,7 @@ dump_file0 (struct tar_stat_info *st, char const *p, } buffer[size] = '\0'; assign_string (&st->link_name, buffer); + transform_name (&st->link_name, XFORM_SYMLINK); if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) < size) write_long_link (st); @@ -1599,7 +1729,7 @@ dump_file0 (struct tar_stat_info *st, char const *p, header = start_header (st); if (!header) return; - tar_copy_str (header->header.linkname, buffer, NAME_FIELD_SIZE); + tar_copy_str (header->header.linkname, st->link_name, NAME_FIELD_SIZE); header->header.typeflag = SYMTYPE; finish_header (st, header, block_ordinal); /* nothing more to do to it */ @@ -1621,12 +1751,14 @@ dump_file0 (struct tar_stat_info *st, char const *p, type = FIFOTYPE; else if (S_ISSOCK (st->stat.st_mode)) { - WARN ((0, 0, _("%s: socket ignored"), quotearg_colon (p))); + WARNOPT (WARN_FILE_IGNORED, + (0, 0, _("%s: socket ignored"), quotearg_colon (p))); return; } else if (S_ISDOOR (st->stat.st_mode)) { - WARN ((0, 0, _("%s: door ignored"), quotearg_colon (p))); + WARNOPT (WARN_FILE_IGNORED, + (0, 0, _("%s: door ignored"), quotearg_colon (p))); return; } else @@ -1665,7 +1797,7 @@ dump_file0 (struct tar_stat_info *st, char const *p, } void -dump_file (char *p, int top_level, dev_t parent_device) +dump_file (const char *p, int top_level, dev_t parent_device) { struct tar_stat_info st; tar_stat_init (&st);