* NEWS: Document new option.
* src/common.h (COMMAND_MTIME): New constant.
* src/create.c (set_mtime_command)
(set_mtime_format): New globals.
(sys_exec_setmtime_script): New prototype.
* src/system.c (start_header): Handle COMMAND_MTIME.
* src/tar.c (sys_exec_setmtime_script): New function.
-GNU tar NEWS - User visible changes. 2023-07-24
+GNU tar NEWS - User visible changes. 2023-08-01
Please send GNU tar bug reports to <bug-tar@gnu.org>
\f
version TBD
* New manual section "Reproducibility", for reproducible tarballs.
+* New options: --set-mtime-command and --set-mtime-format
+
+Both options are valid when archiving files.
+
+** --set-mtime-command=COMMAND
+
+For each FILE being archived, run "COMMAND FILE", parse its
+output as time string and set mtime value of the archive member
+from the result.
+
+Unless --set-mtime-format is also used, the output is parsed
+as argument to --mtime option (see GNU tar manual, chapter 4
+"Date input formats".
+
+** --set-mtime-format=FMT
+
+Defines output format for the COMMAND set by the above option. If
+used, command output will be parsed using strptime(3).
+
\f
version 1.35 - Sergey Poznyakoff, 2023-07-18
USE_FILE_MTIME,
FORCE_MTIME,
CLAMP_MTIME,
+ COMMAND_MTIME,
};
/* Override actual mtime if set to FORCE_MTIME or CLAMP_MTIME */
/* Value to use when forcing or clamping the mtime header field. */
GLOBAL struct timespec mtime_option;
+/* Command to use to set mtime when archiving. */
+GLOBAL char *set_mtime_command;
+
+/* Format (as per strptime(3)) of the output of the above command. If
+ not set, parse_datetime will be used. */
+GLOBAL char *set_mtime_format;
+
/* Return true if mtime_option or newer_mtime_option is initialized. */
#define TIME_OPTION_INITIALIZED(opt) (0 <= (opt).tv_nsec)
const char *archive_name,
int checkpoint_number);
bool mtioseek (bool count_files, off_t count);
+int sys_exec_setmtime_script (const char *script_name,
+ int dirfd,
+ const char *file_name,
+ const char *fmt,
+ struct timespec *ts);
/* Module compare.c */
void report_difference (struct tar_stat_info *st, const char *message, ...)
mtime = timespec_cmp (st->mtime, mtime_option) > 0
? mtime_option : st->mtime;
break;
+
+ case COMMAND_MTIME:
+ if (sys_exec_setmtime_script (set_mtime_command,
+ chdir_fd,
+ st->orig_file_name,
+ set_mtime_format,
+ &mtime))
+ mtime = st->mtime;
+ break;
}
if (archive_format == POSIX_FORMAT)
#include <rmt.h>
#include <signal.h>
#include <wordsplit.h>
+#include <poll.h>
+#include <parse-datetime.h>
static _Noreturn void
xexec (const char *cmd)
FATAL_ERROR ((0, 0, _("Cannot use compressed or remote archives")));
}
+int
+sys_exec_setmtime_script (const char *script_name,
+ int dirfd,
+ const char *file_name,
+ const char *fmt,
+ struct timespec *ts)
+{
+ FATAL_ERROR ((0, 0, _("--set-mtime-command not implemented on this platform")));
+}
#else
extern union block *record_start; /* FIXME */
&& p->stat.st_ino == archive_stat.st_ino);
}
+static char const dev_null[] = "/dev/null";
+
/* Detect if outputting to "/dev/null". */
void
sys_detect_dev_null_output (void)
{
- static char const dev_null[] = "/dev/null";
static struct stat dev_null_stat;
dev_null_output = (strcmp (archive_name_array[0], dev_null) == 0
xexec (script_name);
}
+int
+sys_exec_setmtime_script (const char *script_name,
+ int dirfd,
+ const char *file_name,
+ const char *fmt,
+ struct timespec *ts)
+{
+ pid_t pid;
+ int p[2];
+ int stop = 0;
+ struct pollfd pfd;
+
+ char *buffer = NULL;
+ size_t buflen = 0;
+ size_t bufsize = 0;
+ char *cp;
+ int rc = 0;
+
+ if (pipe (p))
+ FATAL_ERROR ((0, errno, _("pipe failed")));
+
+ if ((pid = xfork ()) == 0)
+ {
+ char *command = xmalloc (strlen (script_name) + strlen (file_name) + 2);
+
+ strcpy (command, script_name);
+ strcat (command, " ");
+ strcat (command, file_name);
+
+ if (dirfd != AT_FDCWD)
+ {
+ if (fchdir (dirfd))
+ FATAL_ERROR ((0, errno, _("chdir failed")));
+ }
+
+ close (0);
+ close (1);
+
+ if (open (dev_null, O_RDONLY) == -1)
+ open_error (dev_null);
+
+ if (dup2 (p[1], 1) == -1)
+ FATAL_ERROR ((0, errno, _("dup2 failed")));
+ close (p[0]);
+
+ priv_set_restore_linkdir ();
+ xexec (command);
+ }
+ close (p[1]);
+
+ pfd.fd = p[0];
+ pfd.events = POLLIN;
+
+ while (1)
+ {
+ int n = poll (&pfd, 1, -1);
+ if (n == -1)
+ {
+ if (errno != EINTR)
+ {
+ ERROR ((0, errno, _("poll failed")));
+ stop = 1;
+ break;
+ }
+ }
+ if (n == 0)
+ break;
+ if (pfd.revents & POLLIN)
+ {
+ if (buflen == bufsize)
+ {
+ if (bufsize == 0)
+ bufsize = BUFSIZ;
+ buffer = x2nrealloc (buffer, &bufsize, 1);
+ }
+ n = read (pfd.fd, buffer + buflen, bufsize - buflen);
+ if (n == -1)
+ {
+ ERROR ((0, errno, _("error reading output of %s"), script_name));
+ stop = 1;
+ break;
+ }
+ if (n == 0)
+ break;
+ buflen += n;
+ }
+ else if (pfd.revents & POLLHUP)
+ break;
+ }
+ close (pfd.fd);
+
+ if (stop)
+ kill (SIGKILL, pid);
+
+ sys_wait_for_child (pid, false);
+
+ if (stop)
+ {
+ free (buffer);
+ return -1;
+ }
+
+ if (buflen == 0)
+ {
+ ERROR ((0, 0, _("empty output from \"%s %s\""), script_name, file_name));
+ return -1;
+ }
+
+ cp = memchr (buffer, '\n', buflen);
+ if (cp)
+ *cp = 0;
+ else
+ {
+ if (buflen == bufsize)
+ buffer = x2nrealloc (buffer, &bufsize, 1);
+ buffer[buflen] = 0;
+ }
+
+ if (fmt)
+ {
+ struct tm tm;
+ time_t t;
+ cp = strptime (buffer, fmt, &tm);
+ if (cp == NULL)
+ {
+ ERROR ((0, 0, _("output from \"%s %s\" does not satisfy format string: %s"),
+ script_name, file_name, buffer));
+ rc = -1;
+ }
+ else if (*cp != 0)
+ {
+ WARN ((0, 0, _("unconsumed output from \"%s %s\": %s"),
+ script_name, file_name, cp));
+ rc = -1;
+ }
+ else
+ {
+ t = mktime (&tm);
+ if (t == (time_t) -1)
+ {
+ ERROR ((0, errno, _("mktime failed")));
+ rc = -1;
+ }
+ else
+ {
+ ts->tv_sec = t;
+ ts->tv_nsec = 0;
+ }
+ }
+ }
+ else if (! parse_datetime (ts, buffer, NULL))
+ {
+ ERROR ((0, 0, _("unparsable output from \"%s %s\": %s"),
+ script_name, file_name, buffer));
+ rc = -1;
+ }
+
+ free (buffer);
+
+ return rc;
+}
+
#endif /* not MSDOS */
XATTR_EXCLUDE,
XATTR_INCLUDE,
ZSTD_OPTION,
+ SET_MTIME_COMMAND_OPTION,
+ SET_MTIME_FORMAT_OPTION,
};
static char const doc[] = N_("\
N_("set mtime for added files from DATE-OR-FILE"), GRID_FATTR },
{"clamp-mtime", CLAMP_MTIME_OPTION, 0, 0,
N_("only set time when the file is more recent than what was given with --mtime"), GRID_FATTR },
+ {"set-mtime-command", SET_MTIME_COMMAND_OPTION, N_("COMMAND"), 0,
+ N_("use output of the COMMAND to set mtime of the stored archive members"),
+ GRID_FATTR },
+ {"set-mtime-format", SET_MTIME_FORMAT_OPTION, N_("FORMAT"), 0,
+ N_("set output format (in the sense of strptime(3)) of the --set-mtime-command command"), GRID_FATTR },
{"mode", MODE_OPTION, N_("CHANGES"), 0,
N_("force (symbolic) mode CHANGES for added files"), GRID_FATTR },
{"atime-preserve", ATIME_PRESERVE_OPTION,
sparse_option = true;
break;
+ case SET_MTIME_COMMAND_OPTION:
+ set_mtime_command = arg;
+ break;
+
+ case SET_MTIME_FORMAT_OPTION:
+ set_mtime_format = arg;
+ break;
+
case SPARSE_VERSION_OPTION:
sparse_option = true;
{
USAGE_ERROR ((0, 0, _("Cannot concatenate compressed archives")));
}
- if (set_mtime_option == CLAMP_MTIME)
+ if (set_mtime_command)
+ {
+ if (set_mtime_option != USE_FILE_MTIME)
+ {
+ USAGE_ERROR ((0, 0,
+ _("--mtime conflicts with --set-mtime-command")));
+ }
+ set_mtime_option = COMMAND_MTIME;
+ }
+ else if (set_mtime_option == CLAMP_MTIME)
{
if (!TIME_OPTION_INITIALIZED (mtime_option))
USAGE_ERROR ((0, 0,