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.
This commit is contained in:
Sergey Poznyakoff
2023-08-01 15:39:15 +03:00
parent eb30aa7801
commit 9a30bb2674
5 changed files with 242 additions and 3 deletions

21
NEWS
View File

@@ -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 <bug-tar@gnu.org>
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

View File

@@ -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, ...)

View File

@@ -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)

View File

@@ -23,6 +23,8 @@
#include <rmt.h>
#include <signal.h>
#include <wordsplit.h>
#include <poll.h>
#include <parse-datetime.h>
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 */

View File

@@ -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,