Переглянути джерело

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.
Sergey Poznyakoff 1 рік тому
батько
коміт
9a30bb2674
5 змінених файлів з 242 додано та 3 видалено
  1. 20 1
      NEWS
  2. 13 0
      src/common.h
  3. 9 0
      src/create.c
  4. 175 1
      src/system.c
  5. 25 1
      src/tar.c

+ 20 - 1
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 <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
 

+ 13 - 0
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, ...)

+ 9 - 0
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)

+ 175 - 1
src/system.c

@@ -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 */

+ 25 - 1
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,