From 9a30bb267430eb1b69151f947cf4613803182c61 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Tue, 1 Aug 2023 15:39:15 +0300 Subject: [PATCH] New option: --set-mtime-command * 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. --- NEWS | 21 +++++- src/common.h | 13 ++++ src/create.c | 9 +++ src/system.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/tar.c | 26 +++++++- 5 files changed, 242 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 5cf09a8a..42a34513 100644 --- a/NEWS +++ b/NEWS @@ -1,10 +1,29 @@ -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 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). + version 1.35 - Sergey Poznyakoff, 2023-07-18 diff --git a/src/common.h b/src/common.h index 9451e132..89912567 100644 --- a/src/common.h +++ b/src/common.h @@ -219,6 +219,7 @@ enum set_mtime_option_mode USE_FILE_MTIME, FORCE_MTIME, CLAMP_MTIME, + COMMAND_MTIME, }; /* Override actual mtime if set to FORCE_MTIME or CLAMP_MTIME */ @@ -226,6 +227,13 @@ GLOBAL enum set_mtime_option_mode set_mtime_option; /* 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) @@ -923,6 +931,11 @@ void sys_exec_checkpoint_script (const char *script_name, 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, ...) diff --git a/src/create.c b/src/create.c index d20178cb..29a747bd 100644 --- a/src/create.c +++ b/src/create.c @@ -843,6 +843,15 @@ start_header (struct tar_stat_info *st) 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) diff --git a/src/system.c b/src/system.c index 93fff885..b7e39f7c 100644 --- a/src/system.c +++ b/src/system.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include static _Noreturn void xexec (const char *cmd) @@ -149,6 +151,15 @@ sys_child_open_for_uncompress (void) 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 */ @@ -183,11 +194,12 @@ sys_file_is_archive (struct tar_stat_info *p) && 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 @@ -916,4 +928,166 @@ sys_exec_checkpoint_script (const char *script_name, 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 */ diff --git a/src/tar.c b/src/tar.c index 7efb0038..98132e79 100644 --- a/src/tar.c +++ b/src/tar.c @@ -350,6 +350,8 @@ enum XATTR_EXCLUDE, XATTR_INCLUDE, ZSTD_OPTION, + SET_MTIME_COMMAND_OPTION, + SET_MTIME_FORMAT_OPTION, }; static char const doc[] = N_("\ @@ -563,6 +565,11 @@ static struct argp_option options[] = { 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, @@ -1706,6 +1713,14 @@ parse_opt (int key, char *arg, struct argp_state *state) 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; { @@ -2525,7 +2540,16 @@ decode_options (int argc, char **argv) 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, -- 2.47.2