+struct link *linklist = NULL; /* points to first link in list */
+\f
+
+/*------------------------------------------------------------------------.
+| Convert VALUE (with substitute SUBSTITUTE if VALUE is out of range) |
+| into a size-SIZE field at WHERE, including a |
+| trailing space. For example, 3 for SIZE means two digits and a space. |
+| |
+| We assume the trailing NUL is already there and don't fill it in. This |
+| fact is used by start_header and finish_header, so don't change it! |
+`------------------------------------------------------------------------*/
+
+/* Output VALUE in octal, 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 SIZE - 1 octal digits (with leading '0's), followed by '\0';
+ but if SIZE octal digits would fit, omit the '\0'. */
+
+static void
+to_oct (uintmax_t value, uintmax_t substitute, char *where, size_t size, const char *type)
+{
+ uintmax_t v = value;
+ size_t i = size;
+
+# define MAX_OCTAL_VAL_WITH_DIGITS(digits) \
+ ((digits) * 3 < sizeof (uintmax_t) * CHAR_BIT \
+ ? ((uintmax_t) 1 << ((digits) * 3)) - 1 \
+ : (uintmax_t) -1)
+
+ /* Output a trailing NUL unless the value is too large. */
+ if (value <= MAX_OCTAL_VAL_WITH_DIGITS (size - 1))
+ where[--i] = '\0';
+
+ /* Produce the digits -- at least one. */
+
+ do
+ {
+ where[--i] = '0' + (int) (v & 7); /* one octal digit */
+ v >>= 3;
+ }
+ while (i != 0 && v != 0);
+
+ /* Leading zeros, if necessary. */
+ while (i != 0)
+ where[--i] = '0';
+
+ if (v != 0)
+ {
+ uintmax_t maxval = MAX_OCTAL_VAL_WITH_DIGITS (size);
+ char buf1[UINTMAX_STRSIZE_BOUND];
+ char buf2[UINTMAX_STRSIZE_BOUND];
+ char buf3[UINTMAX_STRSIZE_BOUND];
+ char *value_string = STRINGIFY_BIGINT (value, buf1);
+ char *maxval_string = STRINGIFY_BIGINT (maxval, buf2);
+ if (substitute)
+ {
+ substitute &= maxval;
+ WARN ((0, 0, _("%s value %s too large (max=%s); substituting %s"),
+ type, value_string, maxval_string,
+ STRINGIFY_BIGINT (substitute, buf3)));
+ to_oct (substitute, (uintmax_t) 0, where, size, type);
+ }
+ else
+ ERROR ((0, 0, _("%s value %s too large (max=%s)"),
+ type, value_string, maxval_string));
+ }
+}
+#ifndef GID_NOBODY
+#define GID_NOBODY 0
+#endif
+void
+gid_to_oct (gid_t v, char *p, size_t s)
+{
+ to_oct ((uintmax_t) v, (uintmax_t) GID_NOBODY, p, s, "gid_t");
+}
+void
+major_to_oct (major_t v, char *p, size_t s)
+{
+ to_oct ((uintmax_t) v, (uintmax_t) 0, p, s, "major_t");
+}
+void
+minor_to_oct (minor_t v, char *p, size_t s)
+{
+ to_oct ((uintmax_t) v, (uintmax_t) 0, p, s, "minor_t");
+}
+void
+mode_to_oct (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. */
+ uintmax_t u =
+ ((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)
+ ? v
+ : ((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_oct (u, (uintmax_t) 0, p, s, "mode_t");
+}
+void
+off_to_oct (off_t v, char *p, size_t s)
+{
+ to_oct ((uintmax_t) v, (uintmax_t) 0, p, s, "off_t");
+}
+void
+size_to_oct (size_t v, char *p, size_t s)
+{
+ to_oct ((uintmax_t) v, (uintmax_t) 0, p, s, "size_t");
+}
+void
+time_to_oct (time_t v, char *p, size_t s)
+{
+ to_oct ((uintmax_t) v, (uintmax_t) 0, p, s, "time_t");
+}
+#ifndef UID_NOBODY
+#define UID_NOBODY 0
+#endif
+void
+uid_to_oct (uid_t v, char *p, size_t s)
+{
+ to_oct ((uintmax_t) v, (uintmax_t) UID_NOBODY, p, s, "uid_t");
+}
+void
+uintmax_to_oct (uintmax_t v, char *p, size_t s)
+{
+ to_oct (v, (uintmax_t) 0, 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;
+
+#if MSDOS
+ if (name[1] == ':')
+ {
+ name += 2;
+ if (!warned_once)
+ {
+ warned_once = 1;
+ WARN ((0, 0, _("Removing drive spec from names in the archive")));
+ }
+ }
+#endif
+
+ 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_OCT (st->st_mode & MODE_ALL, header->header.mode);
+ else
+ MODE_TO_OCT (st->st_mode, header->header.mode);
+
+ UID_TO_OCT (st->st_uid, header->header.uid);
+ GID_TO_OCT (st->st_gid, header->header.gid);
+ OFF_TO_OCT (st->st_size, header->header.size);
+ TIME_TO_OCT (st->st_mtime, header->header.mtime);
+
+ if (incremental_option)
+ if (archive_format == OLDGNU_FORMAT)
+ {
+ TIME_TO_OCT (st->st_atime, header->oldgnu_header.atime);
+ TIME_TO_OCT (st->st_ctime, header->oldgnu_header.ctime);
+ }
+
+ header->header.typeflag = archive_format == V7_FORMAT ? AREGTYPE : REGTYPE;
+
+ switch (archive_format)
+ {
+ case DEFAULT_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;
+ }
+
+ 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_oct.
+ The final space is already there, from
+ checksumming, and to_oct doesn't modify it.
+
+ This is a fast way to do:
+
+ sprintf(header->header.chksum, "%6o", sum); */
+
+ uintmax_to_oct ((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;
+}
+
+/*---.
+| ? |
+`---*/
+
+static void
+init_sparsearray (void)
+{
+ int counter;
+
+ sp_array_size = 10;
+
+ /* Make room for our scratch space -- initially is 10 elts long. */
+
+ sparsearray = (struct sp_array *)
+ xmalloc (sp_array_size * sizeof (struct sp_array));
+ for (counter = 0; counter < sp_array_size; counter++)
+ {
+ sparsearray[counter].offset = 0;
+ sparsearray[counter].numbytes = 0;
+ }
+}
+
+/*---.
+| ? |
+`---*/