+ Written by John Gilmore, on 1985-08-25. */
+
+#include <system.h>
+
+#include <quotearg.h>
+
+#include "common.h"
+#include <hash.h>
+
+/* Error number to use when an impostor is discovered.
+ Pretend the impostor isn't there. */
+enum { IMPOSTOR_ERRNO = ENOENT };
+
+struct link
+ {
+ dev_t dev;
+ ino_t ino;
+ nlink_t nlink;
+ char name[1];
+ };
+
+struct exclusion_tag
+{
+ const char *name;
+ size_t length;
+ enum exclusion_tag_type type;
+ bool (*predicate) (int fd);
+ struct exclusion_tag *next;
+};
+
+static struct exclusion_tag *exclusion_tags;
+
+void
+add_exclusion_tag (const char *name, enum exclusion_tag_type type,
+ bool (*predicate) (int fd))
+{
+ 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 (struct tar_stat_info const *st, char const **tag_file_name)
+{
+ struct exclusion_tag *tag;
+
+ for (tag = exclusion_tags; tag; tag = tag->next)
+ {
+ int tagfd = subfile_open (st, tag->name, open_read_flags);
+ if (0 <= tagfd)
+ {
+ bool satisfied = !tag->predicate || tag->predicate (tagfd);
+ close (tagfd);
+ if (satisfied)
+ {
+ 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 (int fd)
+{
+ char tagbuf[CACHEDIR_SIGNATURE_SIZE];
+
+ return
+ (read (fd, tagbuf, CACHEDIR_SIGNATURE_SIZE) == CACHEDIR_SIGNATURE_SIZE
+ && memcmp (tagbuf, CACHEDIR_SIGNATURE, CACHEDIR_SIGNATURE_SIZE) == 0);
+}
+
+\f
+/* The maximum uintmax_t value that can be represented with DIGITS digits,
+ assuming that each digit is BITS_PER_DIGIT wide. */
+#define MAX_VAL_WITH_DIGITS(digits, bits_per_digit) \
+ ((digits) * (bits_per_digit) < sizeof (uintmax_t) * CHAR_BIT \
+ ? ((uintmax_t) 1 << ((digits) * (bits_per_digit))) - 1 \
+ : (uintmax_t) -1)
+
+/* The maximum uintmax_t value that can be represented with octal
+ digits and a trailing NUL in BUFFER. */
+#define MAX_OCTAL_VAL(buffer) MAX_VAL_WITH_DIGITS (sizeof (buffer) - 1, LG_8)
+
+/* Convert VALUE to an octal representation suitable for tar headers.
+ Output to buffer WHERE with size SIZE.
+ The result is undefined if SIZE is 0 or if VALUE is too large to fit. */
+
+static void
+to_octal (uintmax_t value, char *where, size_t size)
+{
+ uintmax_t v = value;
+ size_t i = size;
+
+ do
+ {
+ where[--i] = '0' + (v & ((1 << LG_8) - 1));
+ v >>= LG_8;
+ }
+ while (i);
+}
+
+/* Copy at most LEN bytes from the string SRC to DST. Terminate with
+ NUL unless SRC is LEN or more bytes long. */
+
+static void
+tar_copy_str (char *dst, const char *src, size_t len)
+{
+ size_t i;
+ for (i = 0; i < len; i++)
+ if (! (dst[i] = src[i]))
+ break;
+}
+
+/* Same as tar_copy_str, but always terminate with NUL if using
+ is OLDGNU format */
+
+static void
+tar_name_copy_str (char *dst, const char *src, size_t len)
+{
+ tar_copy_str (dst, src, len);
+ if (archive_format == OLDGNU_FORMAT)
+ dst[len-1] = 0;
+}
+
+/* Convert NEGATIVE VALUE to a base-256 representation suitable for
+ tar headers. NEGATIVE is 1 if VALUE was negative before being cast
+ to uintmax_t, 0 otherwise. Output to buffer WHERE with size SIZE.
+ The result is undefined if SIZE is 0 or if VALUE is too large to
+ fit. */
+
+static void
+to_base256 (int negative, uintmax_t value, char *where, size_t size)
+{
+ uintmax_t v = value;
+ uintmax_t propagated_sign_bits =
+ ((uintmax_t) - negative << (CHAR_BIT * sizeof v - LG_256));
+ size_t i = size;
+
+ do
+ {
+ where[--i] = v & ((1 << LG_256) - 1);
+ v = propagated_sign_bits | (v >> LG_256);
+ }
+ while (i);
+}
+
+#define GID_TO_CHARS(val, where) gid_to_chars (val, where, sizeof (where))
+#define MAJOR_TO_CHARS(val, where) major_to_chars (val, where, sizeof (where))
+#define MINOR_TO_CHARS(val, where) minor_to_chars (val, where, sizeof (where))
+#define MODE_TO_CHARS(val, where) mode_to_chars (val, where, sizeof (where))
+#define UID_TO_CHARS(val, where) uid_to_chars (val, where, sizeof (where))
+
+#define UNAME_TO_CHARS(name,buf) string_to_chars (name, buf, sizeof(buf))
+#define GNAME_TO_CHARS(name,buf) string_to_chars (name, buf, sizeof(buf))
+
+static bool
+to_chars (int negative, uintmax_t value, size_t valsize,
+ uintmax_t (*substitute) (int *),
+ char *where, size_t size, const char *type);
+
+static bool
+to_chars_subst (int negative, int gnu_format, uintmax_t value, size_t valsize,
+ uintmax_t (*substitute) (int *),
+ char *where, size_t size, const char *type)
+{
+ uintmax_t maxval = (gnu_format
+ ? MAX_VAL_WITH_DIGITS (size - 1, LG_256)
+ : MAX_VAL_WITH_DIGITS (size - 1, LG_8));
+ char valbuf[UINTMAX_STRSIZE_BOUND + 1];
+ char maxbuf[UINTMAX_STRSIZE_BOUND];
+ char minbuf[UINTMAX_STRSIZE_BOUND + 1];
+ 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;
+ char *p = STRINGIFY_BIGINT (m, minbuf + 1);
+ *--p = '-';
+ minval_string = p;
+ }
+ else
+ minval_string = "0";
+
+ if (negative)
+ {
+ char *p = STRINGIFY_BIGINT (- value, valbuf + 1);
+ *--p = '-';
+ value_string = p;
+ }
+ else
+ value_string = STRINGIFY_BIGINT (value, valbuf);
+
+ if (substitute)
+ {
+ int negsub;
+ uintmax_t sub = substitute (&negsub) & maxval;
+ /* 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);
+ if (negsub)
+ *--sub_string = '-';
+ WARN ((0, 0, _("value %s out of %s range %s..%s; substituting %s"),
+ value_string, type, minval_string, maxval_string,
+ sub_string));
+ return to_chars (negsub, s, valsize, 0, where, size, type);
+ }
+ else
+ ERROR ((0, 0, _("value %s out of %s range %s..%s"),
+ value_string, type, minval_string, maxval_string));
+ return false;
+}
+
+/* Convert NEGATIVE VALUE (which was originally of size VALSIZE) to
+ external form, using SUBSTITUTE (...) if VALUE won't fit. Output
+ to buffer WHERE with size SIZE. NEGATIVE is 1 iff VALUE was
+ negative before being cast to uintmax_t; its original bitpattern
+ can be deduced from VALSIZE, its original size before casting.
+ TYPE is the kind of value being output (useful for diagnostics).
+ Prefer the POSIX format of SIZE - 1 octal digits (with leading zero
+ digits), followed by '\0'. If this won't work, and if GNU or
+ OLDGNU format is allowed, use '\200' followed by base-256, or (if
+ NEGATIVE is nonzero) '\377' followed by two's complement base-256.
+ If neither format works, use SUBSTITUTE (...) instead. Pass to
+ SUBSTITUTE the address of an 0-or-1 flag recording whether the
+ substitute value is negative. */
+
+static bool
+to_chars (int negative, uintmax_t value, size_t valsize,
+ uintmax_t (*substitute) (int *),
+ char *where, size_t size, const char *type)
+{
+ int gnu_format = (archive_format == GNU_FORMAT
+ || archive_format == OLDGNU_FORMAT);
+
+ /* Generate the POSIX octal representation if the number fits. */
+ if (! negative && value <= MAX_VAL_WITH_DIGITS (size - 1, LG_8))
+ {
+ where[size - 1] = '\0';
+ to_octal (value, where, size - 1);
+ return true;
+ }
+ else if (gnu_format)
+ {
+ /* Try to cope with the number by using traditional GNU format
+ methods */