+struct link *linklist = NULL; /* points to first link in list */
+\f
+/* Base 64 digits; see Internet RFC 2045 Table 1. */
+char const base_64_digits[64] =
+{
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+#define base_8_digits (base_64_digits + 26 * 2)
+
+/* 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)
+
+/* Convert VALUE to a representation suitable for tar headers,
+ using base 1 << BITS_PER_DIGIT.
+ Use the digits in DIGIT_CHAR[0] ... DIGIT_CHAR[base - 1].
+ 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_base (uintmax_t value, int bits_per_digit, char const *digit_char,
+ char *where, size_t size)
+{
+ uintmax_t v = value;
+ size_t i = size;
+ unsigned digit_mask = (1 << bits_per_digit) - 1;
+
+ do
+ {
+ where[--i] = digit_char[v & digit_mask];
+ v >>= bits_per_digit;
+ }
+ while (i);
+}
+
+/* NEGATIVE is nonzero if VALUE was negative before being cast to
+ uintmax_t; its original bitpattern can be deduced from VALSIZE, its
+ original size before casting. Convert VALUE to external form,
+ using SUBSTITUTE (...) if VALUE won't fit. Output to buffer WHERE
+ with size SIZE. 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 format is allowed, use '+' or '-' followed by SIZE - 1
+ base-64 digits. 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 void
+to_chars (int negative, uintmax_t value, size_t valsize,
+ uintmax_t (*substitute) PARAMS ((int *)),
+ char *where, size_t size, const char *type)
+{
+ uintmax_t v = negative ? -value : value;
+
+ if (! negative && v <= MAX_VAL_WITH_DIGITS (size - 1, LG_8))
+ {
+ where[size - 1] = '\0';
+ to_base (v, LG_8, base_8_digits, where, size - 1);
+ }
+ else if (v <= MAX_VAL_WITH_DIGITS (size - 1, LG_64)
+ && archive_format == GNU_FORMAT)
+ {
+ where[0] = negative ? '-' : '+';
+ to_base (v, LG_64, base_64_digits, where + 1, size - 1);
+ }
+ else if (negative
+ && archive_format != GNU_FORMAT
+ && valsize * CHAR_BIT <= (size - 1) * LG_8)
+ {
+ where[size - 1] = '\0';
+ to_base (value & MAX_VAL_WITH_DIGITS (valsize * CHAR_BIT, 1),
+ LG_8, base_8_digits, where, size - 1);
+ }
+ else
+ {
+ uintmax_t maxval = (archive_format == GNU_FORMAT
+ ? MAX_VAL_WITH_DIGITS (size - 1, LG_64)
+ : MAX_VAL_WITH_DIGITS (size - 1, LG_8));
+ char buf1[UINTMAX_STRSIZE_BOUND + 1];
+ char buf2[UINTMAX_STRSIZE_BOUND + 1];
+ char buf3[UINTMAX_STRSIZE_BOUND + 1];
+ char *value_string = STRINGIFY_BIGINT (v, buf1 + 1);
+ char *maxval_string = STRINGIFY_BIGINT (maxval, buf2 + 1);
+ char const *minval_string =
+ (archive_format == GNU_FORMAT
+ ? "0"
+ : (maxval_string[-1] = '-', maxval_string - 1));
+ if (negative)
+ *--value_string = '-';
+ if (substitute)
+ {
+ int negsub;
+ uintmax_t sub = substitute (&negsub) & maxval;
+ uintmax_t s = (negsub &= archive_format == GNU_FORMAT) ? -sub : sub;
+ char *sub_string = STRINGIFY_BIGINT (s, buf3 + 1);
+ if (negsub)
+ *--sub_string = '-';
+ WARN ((0, 0, _("%s value %s out of range %s..%s; substituting %s"),
+ type, value_string, minval_string, maxval_string,
+ sub_string));
+ to_chars (negsub, s, valsize, NULL, where, size, type);
+ }
+ else
+ ERROR ((0, 0, _("%s value %s out of range %s..%s"),
+ type, value_string, minval_string, maxval_string));
+ }
+}
+
+static uintmax_t
+gid_substitute (int *negative)
+{
+ gid_t r;
+#ifdef GID_NOBODY
+ r = GID_NOBODY;
+#else
+ static gid_t gid_nobody;
+ if (!gid_nobody && !gname_to_gid ("nobody", &gid_nobody))
+ gid_nobody = -2;
+ r = gid_nobody;
+#endif
+ *negative = r < 0;
+ return r;
+}
+
+void
+gid_to_chars (gid_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, gid_substitute, p, s, "gid_t");
+}
+
+void
+major_to_chars (major_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, NULL, p, s, "major_t");
+}
+
+void
+minor_to_chars (minor_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, NULL, p, s, "minor_t");
+}
+
+void
+mode_to_chars (mode_t v, char *p, size_t s)
+{
+ /* In the common case where the internal and external mode bits are the same,
+ propagate all unknown bits to the external mode.
+ This matches historical practice.
+ Otherwise, just copy the bits we know about. */
+ int negative;
+ uintmax_t u;
+ if (S_ISUID == TSUID && S_ISGID == TSGID && S_ISVTX == TSVTX
+ && S_IRUSR == TUREAD && S_IWUSR == TUWRITE && S_IXUSR == TUEXEC
+ && S_IRGRP == TGREAD && S_IWGRP == TGWRITE && S_IXGRP == TGEXEC
+ && S_IROTH == TOREAD && S_IWOTH == TOWRITE && S_IXOTH == TOEXEC)
+ {
+ negative = v < 0;
+ u = v;
+ }
+ else
+ {
+ negative = 0;
+ u = ((v & S_ISUID ? TSUID : 0)
+ | (v & S_ISGID ? TSGID : 0)
+ | (v & S_ISVTX ? TSVTX : 0)
+ | (v & S_IRUSR ? TUREAD : 0)
+ | (v & S_IWUSR ? TUWRITE : 0)
+ | (v & S_IXUSR ? TUEXEC : 0)
+ | (v & S_IRGRP ? TGREAD : 0)
+ | (v & S_IWGRP ? TGWRITE : 0)
+ | (v & S_IXGRP ? TGEXEC : 0)
+ | (v & S_IROTH ? TOREAD : 0)
+ | (v & S_IWOTH ? TOWRITE : 0)
+ | (v & S_IXOTH ? TOEXEC : 0));
+ }
+ to_chars (negative, u, sizeof v, NULL, p, s, "mode_t");
+}
+
+void
+off_to_chars (off_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, NULL, p, s, "off_t");
+}
+
+void
+size_to_chars (size_t v, char *p, size_t s)
+{
+ to_chars (0, (uintmax_t) v, sizeof v, NULL, p, s, "size_t");
+}
+
+void
+time_to_chars (time_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, NULL, p, s, "time_t");
+}
+
+static uintmax_t
+uid_substitute (int *negative)
+{
+ uid_t r;
+#ifdef UID_NOBODY
+ r = UID_NOBODY;
+#else
+ static uid_t uid_nobody;
+ if (!uid_nobody && !uname_to_uid ("nobody", &uid_nobody))
+ uid_nobody = -2;
+ r = uid_nobody;
+#endif
+ *negative = r < 0;
+ return r;
+}
+
+void
+uid_to_chars (uid_t v, char *p, size_t s)
+{
+ to_chars (v < 0, (uintmax_t) v, sizeof v, uid_substitute, p, s, "uid_t");
+}
+
+void
+uintmax_to_chars (uintmax_t v, char *p, size_t s)
+{
+ to_chars (0, v, sizeof v, NULL, p, s, "uintmax_t");
+}
+\f
+/* Writing routines. */
+
+/*-----------------------------------------------------------------------.
+| Just zeroes out the buffer so we don't confuse ourselves with leftover |
+| data. |
+`-----------------------------------------------------------------------*/
+
+static void
+clear_buffer (char *buffer)
+{
+ memset (buffer, 0, BLOCKSIZE);
+}
+
+/*-------------------------------------------------------------------------.
+| Write the EOT block(s). We actually zero at least one block, through |
+| the end of the record. Old tar, as previous versions of GNU tar, writes |
+| garbage after two zeroed blocks. |
+`-------------------------------------------------------------------------*/
+
+void
+write_eot (void)
+{
+ union block *pointer = find_next_block ();
+
+ if (pointer)
+ {
+ size_t space = available_space_after (pointer);
+
+ memset (pointer->buffer, 0, space);
+ set_next_block_after (pointer);
+ }
+}
+
+/*-----------------------------------------------------.
+| Write a GNUTYPE_LONGLINK or GNUTYPE_LONGNAME block. |
+`-----------------------------------------------------*/
+
+/* FIXME: Cross recursion between start_header and write_long! */
+
+static union block *start_header PARAMS ((const char *, struct stat *));
+
+static void
+write_long (const char *p, char type)
+{
+ size_t size = strlen (p) + 1;
+ size_t bufsize;
+ union block *header;
+ struct stat foo;
+
+ memset (&foo, 0, sizeof foo);
+ foo.st_size = size;
+
+ header = start_header ("././@LongLink", &foo);
+ header->header.typeflag = type;
+ finish_header (header);
+
+ header = find_next_block ();
+
+ bufsize = available_space_after (header);
+
+ while (bufsize < size)
+ {
+ memcpy (header->buffer, p, bufsize);
+ p += bufsize;
+ size -= bufsize;
+ set_next_block_after (header + (bufsize - 1) / BLOCKSIZE);
+ header = find_next_block ();
+ bufsize = available_space_after (header);
+ }
+ memcpy (header->buffer, p, size);
+ memset (header->buffer + size, 0, bufsize - size);
+ set_next_block_after (header + (size - 1) / BLOCKSIZE);
+}
+\f
+/* Header handling. */
+
+/*---------------------------------------------------------------------.
+| Make a header block for the file name whose stat info is st. Return |
+| header pointer for success, NULL if the name is too long. |
+`---------------------------------------------------------------------*/
+
+static union block *
+start_header (const char *name, struct stat *st)
+{
+ union block *header;
+
+ if (!absolute_names_option)
+ {
+ static int warned_once = 0;
+ size_t prefix_len = FILESYSTEM_PREFIX_LEN (name);
+
+ if (prefix_len)
+ {
+ name += prefix_len;
+ if (!warned_once)
+ {
+ warned_once = 1;
+ WARN ((0, 0, _("Removing filesystem prefix from names in the archive")));
+ }
+ }
+
+ while (*name == '/')
+ {
+ name++; /* force relative path */
+ if (!warned_once)
+ {
+ warned_once = 1;
+ WARN ((0, 0, _("\
+Removing leading `/' from absolute path names in the archive")));
+ }
+ }
+ }
+
+ /* Check the file name and put it in the block. */
+
+ if (strlen (name) >= (size_t) NAME_FIELD_SIZE)
+ write_long (name, GNUTYPE_LONGNAME);
+ header = find_next_block ();
+ memset (header->buffer, 0, sizeof (union block));
+
+ assign_string (¤t_file_name, name);
+
+ strncpy (header->header.name, name, NAME_FIELD_SIZE);
+ header->header.name[NAME_FIELD_SIZE - 1] = '\0';
+
+ /* Override some stat fields, if requested to do so. */
+
+ if (owner_option != (uid_t) -1)
+ st->st_uid = owner_option;
+ if (group_option != (gid_t) -1)
+ st->st_gid = group_option;
+ if (mode_option)
+ st->st_mode = ((st->st_mode & ~MODE_ALL)
+ | mode_adjust (st->st_mode, mode_option));
+
+ /* Paul Eggert tried the trivial test ($WRITER cf a b; $READER tvf a)
+ for a few tars and came up with the following interoperability
+ matrix:
+
+ WRITER
+ 1 2 3 4 5 6 7 8 9 READER
+ . . . . . . . . . 1 = SunOS 4.2 tar
+ # . . # # . . # # 2 = NEC SVR4.0.2 tar
+ . . . # # . . # . 3 = Solaris 2.1 tar
+ . . . . . . . . . 4 = GNU tar 1.11.1
+ . . . . . . . . . 5 = HP-UX 8.07 tar
+ . . . . . . . . . 6 = Ultrix 4.1
+ . . . . . . . . . 7 = AIX 3.2
+ . . . . . . . . . 8 = Hitachi HI-UX 1.03
+ . . . . . . . . . 9 = Omron UNIOS-B 4.3BSD 1.60Beta
+
+ . = works
+ # = ``impossible file type''
+
+ The following mask for old archive removes the `#'s in column 4
+ above, thus making GNU tar both a universal donor and a universal
+ acceptor for Paul's test. */
+
+ if (archive_format == V7_FORMAT)
+ MODE_TO_CHARS (st->st_mode & MODE_ALL, header->header.mode);
+ else
+ MODE_TO_CHARS (st->st_mode, header->header.mode);
+
+ UID_TO_CHARS (st->st_uid, header->header.uid);
+ GID_TO_CHARS (st->st_gid, header->header.gid);
+ OFF_TO_CHARS (st->st_size, header->header.size);
+ TIME_TO_CHARS (st->st_mtime, header->header.mtime);
+
+ if (incremental_option)
+ if (archive_format == OLDGNU_FORMAT)
+ {
+ TIME_TO_CHARS (st->st_atime, header->oldgnu_header.atime);
+ TIME_TO_CHARS (st->st_ctime, header->oldgnu_header.ctime);
+ }
+
+ header->header.typeflag = archive_format == V7_FORMAT ? AREGTYPE : REGTYPE;
+
+ switch (archive_format)
+ {
+ case V7_FORMAT:
+ break;
+
+ case OLDGNU_FORMAT:
+ /* Overwrite header->header.magic and header.version in one blow. */
+ strcpy (header->header.magic, OLDGNU_MAGIC);
+ break;
+
+ case POSIX_FORMAT:
+ case GNU_FORMAT:
+ strncpy (header->header.magic, TMAGIC, TMAGLEN);
+ strncpy (header->header.version, TVERSION, TVERSLEN);
+ break;
+
+ default:
+ abort ();
+ }
+
+ if (archive_format == V7_FORMAT || numeric_owner_option)
+ {
+ /* header->header.[ug]name are left as the empty string. */
+ }
+ else
+ {
+ uid_to_uname (st->st_uid, header->header.uname);
+ gid_to_gname (st->st_gid, header->header.gname);
+ }
+
+ return header;
+}
+
+/*-------------------------------------------------------------------------.
+| Finish off a filled-in header block and write it out. We also print the |
+| file name and/or full info if verbose is on. |
+`-------------------------------------------------------------------------*/
+
+void
+finish_header (union block *header)
+{
+ size_t i;
+ int sum;
+ char *p;
+
+ memcpy (header->header.chksum, CHKBLANKS, sizeof (header->header.chksum));
+
+ sum = 0;
+ p = header->buffer;
+ for (i = sizeof (*header); i-- != 0; )
+ /* We can't use unsigned char here because of old compilers, e.g. V7. */
+ sum += 0xFF & *p++;
+
+ /* Fill in the checksum field. It's formatted differently from the
+ other fields: it has [6] digits, a null, then a space -- rather than
+ digits, then a null. We use to_chars.
+ The final space is already there, from
+ checksumming, and to_chars doesn't modify it.
+
+ This is a fast way to do:
+
+ sprintf(header->header.chksum, "%6o", sum); */
+
+ uintmax_to_chars ((uintmax_t) sum, header->header.chksum, 7);
+
+ set_next_block_after (header);
+
+ if (verbose_option
+ && header->header.typeflag != GNUTYPE_LONGLINK
+ && header->header.typeflag != GNUTYPE_LONGNAME)
+ {
+ /* These globals are parameters to print_header, sigh. */
+
+ current_header = header;
+ /* current_stat is already set up. */
+ current_format = archive_format;
+ print_header ();
+ }
+}
+\f
+/* Sparse file processing. */
+
+/*-------------------------------------------------------------------------.
+| Takes a blockful of data and basically cruises through it to see if it's |
+| made *entirely* of zeros, returning a 0 the instant it finds something |
+| that is a nonzero, i.e., useful data. |
+`-------------------------------------------------------------------------*/
+
+static int
+zero_block_p (char *buffer)
+{
+ int counter;
+
+ for (counter = 0; counter < BLOCKSIZE; counter++)
+ if (buffer[counter] != '\0')
+ return 0;
+ return 1;
+}
+
+/*---.
+| ? |
+`---*/