+/* Version of incremental format snapshots (directory files) used by this
+ tar. Currently it is supposed to be a single decimal number. 0 means
+ incremental snapshots as per tar version before 1.15.2.
+
+ The current tar version supports incremental versions from
+ 0 up to TAR_INCREMENTAL_VERSION, inclusive.
+ It is able to create only snapshots of TAR_INCREMENTAL_VERSION */
+
+#define TAR_INCREMENTAL_VERSION 2
+
+/* Read incremental snapshot formats 0 and 1 */
+static void
+read_incr_db_01 (int version, const char *initbuf)
+{
+ int n;
+ uintmax_t u;
+ char *buf = NULL;
+ size_t bufsize = 0;
+ char *ebuf;
+ long lineno = 1;
+
+ if (version == 1)
+ {
+ if (getline (&buf, &bufsize, listed_incremental_stream) <= 0)
+ {
+ read_error (listed_incremental_option);
+ free (buf);
+ return;
+ }
+ ++lineno;
+ }
+ else
+ {
+ buf = strdup (initbuf);
+ bufsize = strlen (buf) + 1;
+ }
+
+ newer_mtime_option = decode_timespec (buf, &ebuf, false);
+
+ if (! valid_timespec (newer_mtime_option))
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option),
+ lineno,
+ _("Invalid time stamp")));
+ else
+ {
+ 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")));
+ newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
+ newer_mtime_option.tv_nsec = -1;
+ }
+ else
+ newer_mtime_option.tv_nsec = u;
+ }
+ }
+
+ while (0 < (n = getline (&buf, &bufsize, listed_incremental_stream)))
+ {
+ dev_t dev;
+ ino_t ino;
+ bool nfs = buf[0] == '+';
+ char *strp = buf + nfs;
+ struct timespec mtime;
+
+ lineno++;
+
+ if (buf[n - 1] == '\n')
+ buf[n - 1] = '\0';
+
+ if (version == 1)
+ {
+ mtime = decode_timespec (strp, &ebuf, false);
+ strp = ebuf;
+ if (!valid_timespec (mtime) || *strp != ' ')
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid modification time")));
+
+ errno = 0;
+ 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)")));
+ mtime.tv_nsec = -1;
+ }
+ else
+ mtime.tv_nsec = u;
+ strp = ebuf;
+ }
+ else
+ mtime.tv_sec = mtime.tv_nsec = 0;
+
+ dev = strtosysint (strp, &ebuf,
+ TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t));
+ strp = ebuf;
+ if (errno || *strp != ' ')
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid device number")));
+
+ ino = strtosysint (strp, &ebuf,
+ TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t));
+ strp = ebuf;
+ if (errno || *strp != ' ')
+ ERROR ((0, errno, "%s:%ld: %s",
+ quotearg_colon (listed_incremental_option), lineno,
+ _("Invalid inode number")));
+
+ strp++;
+ unquote_string (strp);
+ note_directory (strp, mtime, dev, ino, nfs, false, NULL);
+ }
+ free (buf);
+}
+
+/* Read a nul-terminated string from FP and store it in STK.
+ Store the number of bytes read (including nul terminator) in PCOUNT.
+
+ Return the last character read or EOF on end of file. */
+static int
+read_obstack (FILE *fp, struct obstack *stk, size_t *pcount)
+{
+ int c;
+ size_t i;
+
+ for (i = 0, c = getc (fp); c != EOF && c != 0; c = getc (fp), i++)
+ obstack_1grow (stk, c);
+ obstack_1grow (stk, 0);
+
+ *pcount = i;
+ return c;
+}
+
+/* Read from file FP a null-terminated string and convert it to an
+ integer. FIELDNAME is the intended use of the integer, useful for
+ diagnostics. MIN_VAL and MAX_VAL are its minimum and maximum
+ permissible values; MIN_VAL must be nonpositive and MAX_VAL positive.
+ Store into *PVAL the resulting value, converted to intmax_t.
+
+ Throw a fatal error if the string cannot be converted or if the
+ converted value is out of range.
+
+ Return true if successful, false if end of file. */
+
+static bool
+read_num (FILE *fp, char const *fieldname,
+ intmax_t min_val, uintmax_t max_val, intmax_t *pval)
+{
+ int i;
+ char buf[INT_BUFSIZE_BOUND (intmax_t)];
+ char offbuf[INT_BUFSIZE_BOUND (off_t)];
+ char minbuf[INT_BUFSIZE_BOUND (intmax_t)];
+ char maxbuf[INT_BUFSIZE_BOUND (intmax_t)];
+ int conversion_errno;
+ int c = getc (fp);
+ bool negative = c == '-';
+
+ for (i = 0; (i == 0 && negative) || ISDIGIT (c); i++)
+ {
+ buf[i] = c;
+ if (i == sizeof buf - 1)
+ FATAL_ERROR ((0, 0,
+ _("%s: byte %s: %s %.*s... too long"),
+ quotearg_colon (listed_incremental_option),
+ offtostr (ftello (fp), offbuf),
+ fieldname, i + 1, buf));
+ c = getc (fp);
+ }
+
+ buf[i] = 0;
+
+ if (c < 0)
+ {
+ if (ferror (fp))
+ read_fatal (listed_incremental_option);
+ if (i != 0)
+ FATAL_ERROR ((0, 0, "%s: %s",
+ quotearg_colon (listed_incremental_option),
+ _("Unexpected EOF in snapshot file")));
+ return false;
+ }
+
+ if (c)
+ FATAL_ERROR ((0, 0,
+ _("%s: byte %s: %s %s followed by invalid byte 0x%02x"),
+ quotearg_colon (listed_incremental_option),
+ offtostr (ftello (fp), offbuf),
+ fieldname, buf, c));
+
+ *pval = strtosysint (buf, NULL, min_val, max_val);
+ conversion_errno = errno;
+
+ switch (conversion_errno)
+ {
+ case ERANGE:
+ FATAL_ERROR ((0, conversion_errno,
+ _("%s: byte %s: (valid range %s..%s)\n\t%s %s"),
+ quotearg_colon (listed_incremental_option),
+ offtostr (ftello (fp), offbuf),
+ imaxtostr (min_val, minbuf),
+ umaxtostr (max_val, maxbuf), fieldname, buf));
+ default:
+ FATAL_ERROR ((0, conversion_errno,
+ _("%s: byte %s: %s %s"),
+ quotearg_colon (listed_incremental_option),
+ offtostr (ftello (fp), offbuf), fieldname, buf));
+ case 0:
+ break;
+ }
+
+ return true;
+}
+
+/* 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)
+{
+ intmax_t s, ns;
+
+ if (read_num (fp, "sec", TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t), &s)
+ && read_num (fp, "nsec", 0, BILLION - 1, &ns))
+ {
+ pval->tv_sec = s;
+ pval->tv_nsec = ns;
+ }
+ else
+ {
+ FATAL_ERROR ((0, 0, "%s: %s",
+ quotearg_colon (listed_incremental_option),
+ _("Unexpected EOF in snapshot file")));
+ }
+}
+
+/* Read incremental snapshot format 2 */
+static void
+read_incr_db_2 (void)
+{
+ struct obstack stk;
+ char offbuf[INT_BUFSIZE_BOUND (off_t)];
+
+ obstack_init (&stk);
+
+ read_timespec (listed_incremental_stream, &newer_mtime_option);
+
+ for (;;)
+ {
+ intmax_t i;
+ struct timespec mtime;
+ dev_t dev;
+ ino_t ino;
+ bool nfs;
+ char *name;
+ char *content;
+ size_t s;
+
+ if (! read_num (listed_incremental_stream, "nfs", 0, 1, &i))
+ return; /* Normal return */
+
+ nfs = i;
+
+ read_timespec (listed_incremental_stream, &mtime);
+
+ if (! read_num (listed_incremental_stream, "dev",
+ TYPE_MINIMUM (dev_t), TYPE_MAXIMUM (dev_t), &i))
+ break;
+ dev = i;
+
+ if (! read_num (listed_incremental_stream, "ino",
+ TYPE_MINIMUM (ino_t), TYPE_MAXIMUM (ino_t), &i))
+ break;
+ ino = i;
+
+ if (read_obstack (listed_incremental_stream, &stk, &s))
+ break;
+
+ name = obstack_finish (&stk);
+
+ while (read_obstack (listed_incremental_stream, &stk, &s) == 0 && s > 1)
+ ;
+ if (getc (listed_incremental_stream) != 0)
+ FATAL_ERROR ((0, 0, _("%s: byte %s: %s"),
+ quotearg_colon (listed_incremental_option),
+ offtostr (ftello (listed_incremental_stream), offbuf),
+ _("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 in snapshot file")));
+}
+
+/* Read incremental snapshot file (directory file).
+ If the file has older incremental version, make sure that it is processed
+ correctly and that tar will use the most conservative backup method among
+ possible alternatives (i.e. prefer ALL_CHILDREN over CHANGED_CHILDREN,
+ etc.) This ensures that the snapshots are updated to the recent version
+ without any loss of data. */