瀏覽代碼

New option --clamp-mtime

The new `--clamp-mtime` option will change the behavior of `--mtime` to only
use the time specified if the file mtime is newer than the given time.
The `--clamp-mtime` option can only be used together with `--mtime`.

Typical use case is to make builds reproducible: to loose less
information, it's better to keep the original date of an archive, except for
files modified during the build process. In that case, using a reference
(and thus reproducible) timestamps for the latter is good enough. See
<https://wiki.debian.org/ReproducibleBuilds> for more information.

Patch submitted by Jeremy Bobbio and
Daniel Kahn Gillmor <dkg@fifthhorseman.net>

* doc/tar.1: Document --clamp-mtime
* doc/tar.texi: Likewise.

* src/common.h (set_mtime_option_mode): New enum
(set_mtime_option): Change type to enum set_mtime_option_mode.
(NEWER_OPTION_INITIALIZED): Rename to NEWER_OPTION_INITIALIZED.
* src/create.c (start_header): Set mtime depending on set_mtime_option.
* src/tar.c (options,parse_opt): New option --clamp-mtime
(decode_options): Initialize mtime_option

* tests/time02.at: New testcase.
* tests/Makefile.am: Add new testcase
* tests/testsuite.at: Likewise.
Jeremy Bobbio 9 年之前
父節點
當前提交
13d04fe6ae
共有 9 個文件被更改,包括 120 次插入11 次删除
  1. 4 1
      doc/tar.1
  2. 21 0
      doc/tar.texi
  3. 12 5
      src/common.h
  4. 18 1
      src/create.c
  5. 1 1
      src/list.c
  6. 20 3
      src/tar.c
  7. 1 0
      tests/Makefile.am
  8. 1 0
      tests/testsuite.at
  9. 42 0
      tests/time02.at

+ 4 - 1
doc/tar.1

@@ -13,7 +13,7 @@
 .\"
 .\"
 .\" You should have received a copy of the GNU General Public License
 .\" You should have received a copy of the GNU General Public License
 .\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
 .\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
-.TH TAR 1 "March 16, 2016" "TAR" "GNU TAR Manual"
+.TH TAR 1 "March 23, 2016" "TAR" "GNU TAR Manual"
 .SH NAME
 .SH NAME
 tar \- an archiving utility
 tar \- an archiving utility
 .SH SYNOPSIS
 .SH SYNOPSIS
@@ -1051,6 +1051,9 @@ Display progress messages every \fIN\fRth record (default 10).
 \fB\-\-checkpoint\-action\fR=\fIACTION\fR
 \fB\-\-checkpoint\-action\fR=\fIACTION\fR
 Run \fIACTION\fR on each checkpoint.
 Run \fIACTION\fR on each checkpoint.
 .TP
 .TP
+\fB\-\-clamp\-mtime\fR
+Only set time when the file is more recent than what was given with \-\-mtime.
+.TP
 \fB\-\-full\-time\fR
 \fB\-\-full\-time\fR
 Print file time to its full resolution.
 Print file time to its full resolution.
 .TP
 .TP

+ 21 - 0
doc/tar.texi

@@ -2585,6 +2585,11 @@ complies to UNIX98, was introduced with version
 writing the archive.  This allows you to directly act on archives
 writing the archive.  This allows you to directly act on archives
 while saving space.  @xref{gzip}.
 while saving space.  @xref{gzip}.
 
 
+@opsummary{clamp-mtime}
+@item --clamp-mtime
+
+(See @option{--mtime}.)
+
 @opsummary{confirmation}
 @opsummary{confirmation}
 @item --confirmation
 @item --confirmation
 
 
@@ -2985,6 +2990,11 @@ either a textual date representation (@pxref{Date input formats}) or a
 name of the existing file, starting with @samp{/} or @samp{.}.  In the
 name of the existing file, starting with @samp{/} or @samp{.}.  In the
 latter case, the modification time of that file is used. @xref{override}.
 latter case, the modification time of that file is used. @xref{override}.
 
 
+When @command{--clamp-mtime} is also specified, files with
+modification times earlier than @var{date} will retain their actual
+modification times, and @var{date} will only be used for files whose
+modification times are later than @var{date}.
+
 @opsummary{multi-volume}
 @opsummary{multi-volume}
 @item --multi-volume
 @item --multi-volume
 @itemx -M
 @itemx -M
@@ -5445,6 +5455,17 @@ tar: Option --mtime: Treating date 'yesterday' as 2006-06-20
 @dots{}
 @dots{}
 @end smallexample
 @end smallexample
 
 
+@noindent
+When used with @option{--clamp-mtime} @GNUTAR{} will only set the
+modification date to @var{date} on files whose actual modification
+date is later than @var{date}.  This is to make it easy to build
+reproducible archives given a common timestamp for generated files
+while still retaining the original timestamps of untouched files.
+
+@smallexample
+$ @kbd{tar -c -f archive.tar --clamp-mtime --mtime=@atchar{}$SOURCE_DATE_EPOCH .}
+@end smallexample
+
 @item --owner=@var{user}
 @item --owner=@var{user}
 @opindex owner
 @opindex owner
 
 

+ 12 - 5
src/common.h

@@ -211,13 +211,20 @@ GLOBAL bool multi_volume_option;
    do not get archived (also see after_date_option above).  */
    do not get archived (also see after_date_option above).  */
 GLOBAL struct timespec newer_mtime_option;
 GLOBAL struct timespec newer_mtime_option;
 
 
-/* If true, override actual mtime (see below) */
-GLOBAL bool set_mtime_option;
-/* Value to be put in mtime header field instead of the actual mtime */
+enum set_mtime_option_mode
+{
+  USE_FILE_MTIME,
+  FORCE_MTIME,
+  CLAMP_MTIME,
+};
+
+/* Override actual mtime if set to FORCE_MTIME or CLAMP_MTIME */
+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;
 GLOBAL struct timespec mtime_option;
 
 
-/* Return true if newer_mtime_option is initialized.  */
-#define NEWER_OPTION_INITIALIZED(opt) (0 <= (opt).tv_nsec)
+/* Return true if mtime_option or newer_mtime_option is initialized.  */
+#define TIME_OPTION_INITIALIZED(opt) (0 <= (opt).tv_nsec)
 
 
 /* Return true if the struct stat ST's M time is less than
 /* Return true if the struct stat ST's M time is less than
    newer_mtime_option.  */
    newer_mtime_option.  */

+ 18 - 1
src/create.c

@@ -823,7 +823,24 @@ start_header (struct tar_stat_info *st)
   }
   }
 
 
   {
   {
-    struct timespec mtime = set_mtime_option ? mtime_option : st->mtime;
+    struct timespec mtime;
+
+    switch (set_mtime_option)
+      {
+      case USE_FILE_MTIME:
+	mtime = st->mtime;
+	break;
+	  
+      case FORCE_MTIME:
+	mtime = mtime_option;
+	break;
+	  
+      case CLAMP_MTIME:
+	mtime = timespec_cmp (st->mtime, mtime_option) > 0
+	           ? mtime_option : st->mtime;
+	break;
+      }
+
     if (archive_format == POSIX_FORMAT)
     if (archive_format == POSIX_FORMAT)
       {
       {
 	if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec
 	if (MAX_OCTAL_VAL (header->header.mtime) < mtime.tv_sec

+ 1 - 1
src/list.c

@@ -195,7 +195,7 @@ read_and (void (*do_something) (void))
 	  decode_header (current_header, &current_stat_info,
 	  decode_header (current_header, &current_stat_info,
 			 &current_format, 1);
 			 &current_format, 1);
 	  if (! name_match (current_stat_info.file_name)
 	  if (! name_match (current_stat_info.file_name)
-	      || (NEWER_OPTION_INITIALIZED (newer_mtime_option)
+	      || (TIME_OPTION_INITIALIZED (newer_mtime_option)
 		  /* FIXME: We get mtime now, and again later; this causes
 		  /* FIXME: We get mtime now, and again later; this causes
 		     duplicate diagnostics if header.mtime is bogus.  */
 		     duplicate diagnostics if header.mtime is bogus.  */
 		  && ((mtime.tv_sec
 		  && ((mtime.tv_sec

+ 20 - 3
src/tar.c

@@ -276,6 +276,7 @@ enum
   CHECK_DEVICE_OPTION,
   CHECK_DEVICE_OPTION,
   CHECKPOINT_OPTION,
   CHECKPOINT_OPTION,
   CHECKPOINT_ACTION_OPTION,
   CHECKPOINT_ACTION_OPTION,
+  CLAMP_MTIME_OPTION,
   DELAY_DIRECTORY_RESTORE_OPTION,
   DELAY_DIRECTORY_RESTORE_OPTION,
   HARD_DEREFERENCE_OPTION,
   HARD_DEREFERENCE_OPTION,
   DELETE_OPTION,
   DELETE_OPTION,
@@ -514,6 +515,8 @@ static struct argp_option options[] = {
    N_("use FILE to map file owner GIDs and names"), GRID+1 },
    N_("use FILE to map file owner GIDs and names"), GRID+1 },
   {"mtime", MTIME_OPTION, N_("DATE-OR-FILE"), 0,
   {"mtime", MTIME_OPTION, N_("DATE-OR-FILE"), 0,
    N_("set mtime for added files from DATE-OR-FILE"), GRID+1 },
    N_("set mtime for added files from DATE-OR-FILE"), GRID+1 },
+  {"clamp-mtime", CLAMP_MTIME_OPTION, 0, 0,
+   N_("only set time when the file is more recent than what was given with --mtime"), GRID+1 },
   {"mode", MODE_OPTION, N_("CHANGES"), 0,
   {"mode", MODE_OPTION, N_("CHANGES"), 0,
    N_("force (symbolic) mode CHANGES for added files"), GRID+1 },
    N_("force (symbolic) mode CHANGES for added files"), GRID+1 },
   {"atime-preserve", ATIME_PRESERVE_OPTION,
   {"atime-preserve", ATIME_PRESERVE_OPTION,
@@ -1364,6 +1367,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
       set_subcommand_option (CREATE_SUBCOMMAND);
       set_subcommand_option (CREATE_SUBCOMMAND);
       break;
       break;
 
 
+    case CLAMP_MTIME_OPTION:
+      set_mtime_option = CLAMP_MTIME;
+      break;
+
     case 'd':
     case 'd':
       set_subcommand_option (DIFF_SUBCOMMAND);
       set_subcommand_option (DIFF_SUBCOMMAND);
       break;
       break;
@@ -1505,7 +1512,8 @@ parse_opt (int key, char *arg, struct argp_state *state)
 
 
     case MTIME_OPTION:
     case MTIME_OPTION:
       get_date_or_file (args, "--mtime", arg, &mtime_option);
       get_date_or_file (args, "--mtime", arg, &mtime_option);
-      set_mtime_option = true;
+      if (set_mtime_option == USE_FILE_MTIME)
+        set_mtime_option = FORCE_MTIME;
       break;
       break;
 
 
     case 'n':
     case 'n':
@@ -1521,7 +1529,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       /* Fall through.  */
       /* Fall through.  */
 
 
     case NEWER_MTIME_OPTION:
     case NEWER_MTIME_OPTION:
-      if (NEWER_OPTION_INITIALIZED (newer_mtime_option))
+      if (TIME_OPTION_INITIALIZED (newer_mtime_option))
 	USAGE_ERROR ((0, 0, _("More than one threshold date")));
 	USAGE_ERROR ((0, 0, _("More than one threshold date")));
       get_date_or_file (args,
       get_date_or_file (args,
 			key == NEWER_MTIME_OPTION ? "--newer-mtime"
 			key == NEWER_MTIME_OPTION ? "--newer-mtime"
@@ -2236,6 +2244,8 @@ decode_options (int argc, char **argv)
 
 
   newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
   newer_mtime_option.tv_sec = TYPE_MINIMUM (time_t);
   newer_mtime_option.tv_nsec = -1;
   newer_mtime_option.tv_nsec = -1;
+  mtime_option.tv_sec = TYPE_MINIMUM (time_t);
+  mtime_option.tv_nsec = -1;
   recursion_option = FNM_LEADING_DIR;
   recursion_option = FNM_LEADING_DIR;
   unquote_option = true;
   unquote_option = true;
   tar_sparse_major = 1;
   tar_sparse_major = 1;
@@ -2397,7 +2407,7 @@ decode_options (int argc, char **argv)
 		  _("Multiple archive files require '-M' option")));
 		  _("Multiple archive files require '-M' option")));
 
 
   if (listed_incremental_option
   if (listed_incremental_option
-      && NEWER_OPTION_INITIALIZED (newer_mtime_option))
+      && TIME_OPTION_INITIALIZED (newer_mtime_option))
     {
     {
       struct option_locus *listed_loc = optloc_lookup (OC_LISTED_INCREMENTAL);
       struct option_locus *listed_loc = optloc_lookup (OC_LISTED_INCREMENTAL);
       struct option_locus *newer_loc = optloc_lookup (OC_NEWER);
       struct option_locus *newer_loc = optloc_lookup (OC_NEWER);
@@ -2464,6 +2474,13 @@ decode_options (int argc, char **argv)
 	USAGE_ERROR ((0, 0, _("Cannot concatenate compressed archives")));
 	USAGE_ERROR ((0, 0, _("Cannot concatenate compressed archives")));
     }
     }
 
 
+  if (set_mtime_option == CLAMP_MTIME)
+    {
+      if (!TIME_OPTION_INITIALIZED (mtime_option))
+	USAGE_ERROR ((0, 0,
+		      _("--clamp-mtime needs a date specified using --mtime")));
+    }
+
   /* It is no harm to use --pax-option on non-pax archives in archive
   /* It is no harm to use --pax-option on non-pax archives in archive
      reading mode. It may even be useful, since it allows to override
      reading mode. It may even be useful, since it allows to override
      file attributes from tar headers. Therefore I allow such usage.
      file attributes from tar headers. Therefore I allow such usage.

+ 1 - 0
tests/Makefile.am

@@ -218,6 +218,7 @@ TESTSUITE_AT = \
  spmvp01.at\
  spmvp01.at\
  spmvp10.at\
  spmvp10.at\
  time01.at\
  time01.at\
+ time02.at\
  truncate.at\
  truncate.at\
  update.at\
  update.at\
  update01.at\
  update01.at\

+ 1 - 0
tests/testsuite.at

@@ -358,6 +358,7 @@ m4_include([lustar03.at])
 m4_include([old.at])
 m4_include([old.at])
 
 
 m4_include([time01.at])
 m4_include([time01.at])
+m4_include([time02.at])
 
 
 AT_BANNER([Multivolume archives])
 AT_BANNER([Multivolume archives])
 m4_include([multiv01.at])
 m4_include([multiv01.at])

+ 42 - 0
tests/time02.at

@@ -0,0 +1,42 @@
+# Test clamping mtime GNU tar.  -*- Autotest -*-
+#
+# Copyright 2016 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# written by Daniel Kahn Gillmor
+
+AT_SETUP([time: clamping mtime])
+AT_KEYWORDS([time time02])
+
+AT_TAR_CHECK([
+export TZ=UTC0
+mkdir dir
+
+touch -d 2015-12-01T00:00:00 dir/a >/dev/null 2>&1 || AT_SKIP_TEST
+touch -d 2016-01-01T00:00:00 dir/b >/dev/null 2>&1 || AT_SKIP_TEST
+touch -d 2016-02-01T00:00:00 dir/c >/dev/null 2>&1 || AT_SKIP_TEST
+touch -d 2038-01-01T00:00:00 dir/d >/dev/null 2>&1 || AT_SKIP_TEST
+
+tar -c --mtime 2016-01-15T00:00:00 --clamp-mtime -f archive.tar dir
+tar -d -f archive.tar dir
+],
+[1],
+[
+dir/c: Mod time differs
+dir/d: Mod time differs
+], [], [], [],
+[pax])
+
+AT_CLEANUP