bug-tar
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Bug-tar] mtime in x header


From: Sergey Poznyakoff
Subject: Re: [Bug-tar] mtime in x header
Date: Wed, 07 Oct 2009 21:23:46 +0300

Hi Michael,

Following our discussion, I've applied the attached patches to
the Git master. Their two main effects are:

1. Modification times in the ustar header blocks for the
extended headers are set to the mtimes of the corresponding archive
members. Thus, to ensure binary equivalence of the two PAX archives
with the same contents, the following option is sufficient:

  --pax-option=exthdr.name=%d/PaxHeaders/%f,atime:=0

2. Any value in the --pax-option argument may contain a
time reference, provided that it is enclosed in curly
braces, e.g.:

  --pax-option='atime:={now -2 days},address@hidden'

Thanks & regards,
Sergey

>From 7cb84c25ee51f443c69443e217efe194d12678ea Mon Sep 17 00:00:00 2001
From: Sergey Poznyakoff <address@hidden>
Date: Wed, 7 Oct 2009 18:40:07 +0300
Subject: [PATCH 2/3] Use file's mtime as mtime for its extended header.

This makes two pax archives binary equivalent if they
have the same contents and care is taken to make extended
headers otherwise reproducible, e.g. by using:

  --pax-option=exthdr.name=%d/PaxHeaders/%f,atime:=0

Proposed by Michael D. Adams <address@hidden>.

* src/common.h (start_private_header): Take time_t as 3rd param.
(xheader_write): Likewise.
* src/create.c (start_private_header): Take time_t as 3rd param.
All callers updated.
(write_extended): Use file's mtime as mtime for its extended header,
Use current time stamp as mtime for global headers.
(xheader_write): Take time_t as 3rd param.
---
 src/common.h  |    4 ++--
 src/create.c  |   25 +++++++++++++------------
 src/xheader.c |    6 +++---
 3 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/src/common.h b/src/common.h
index c2a92d2..0020f08 100644
--- a/src/common.h
+++ b/src/common.h
@@ -452,7 +452,7 @@ void finish_header (struct tar_stat_info *st, union block 
*header,
 void simple_finish_header (union block *header);
 union block * write_extended (bool global, struct tar_stat_info *st,
                              union block *old_header);
-union block *start_private_header (const char *name, size_t size);
+union block *start_private_header (const char *name, size_t size, time_t t);
 void write_eot (void);
 void check_links (void);
 void exclusion_tag_warning (const char *dirname, const char *tagname,
@@ -717,7 +717,7 @@ void xheader_decode_global (struct xheader *xhdr);
 void xheader_store (char const *keyword, struct tar_stat_info *st,
                    void const *data);
 void xheader_read (struct xheader *xhdr, union block *header, size_t size);
-void xheader_write (char type, char *name, struct xheader *xhdr);
+void xheader_write (char type, char *name, time_t t, struct xheader *xhdr);
 void xheader_write_global (struct xheader *xhdr);
 void xheader_finish (struct xheader *hdr);
 void xheader_destroy (struct xheader *hdr);
diff --git a/src/create.c b/src/create.c
index d4b9ae7..a964bc2 100644
--- a/src/create.c
+++ b/src/create.c
@@ -515,9 +515,8 @@ write_eot (void)
 
 /* Write a "private" header */
 union block *
-start_private_header (const char *name, size_t size)
+start_private_header (const char *name, size_t size, time_t t)
 {
-  time_t t;
   union block *header = find_next_block ();
 
   memset (header->buffer, 0, sizeof (union block));
@@ -525,7 +524,6 @@ start_private_header (const char *name, size_t size)
   tar_name_copy_str (header->header.name, name, NAME_FIELD_SIZE);
   OFF_TO_CHARS (size, header->header.size);
 
-  time (&t);
   TIME_TO_CHARS (t, header->header.mtime);
   MODE_TO_CHARS (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, header->header.mode);
   UID_TO_CHARS (getuid (), header->header.uid);
@@ -563,13 +561,13 @@ write_gnu_long_link (struct tar_stat_info *st, const char 
*p, char type)
   union block *header;
   char *tmpname;
 
-  header = start_private_header ("././@LongLink", size);
-  FILL(header->header.mtime, '0');
-  FILL(header->header.mode, '0');
-  FILL(header->header.uid, '0');
-  FILL(header->header.gid, '0');
-  FILL(header->header.devmajor, 0);
-  FILL(header->header.devminor, 0);
+  header = start_private_header ("././@LongLink", size, time (NULL));
+  FILL (header->header.mtime, '0');
+  FILL (header->header.mode, '0');
+  FILL (header->header.uid, '0');
+  FILL (header->header.gid, '0');
+  FILL (header->header.devmajor, 0);
+  FILL (header->header.devminor, 0);
   uid_to_uname (0, &tmpname);
   UNAME_TO_CHARS (tmpname, header->header.uname);
   free (tmpname);
@@ -712,7 +710,8 @@ write_extended (bool global, struct tar_stat_info *st, 
union block *old_header)
   union block *header, hp;
   char *p;
   int type;
-
+  time_t t;
+  
   if (st->xhdr.buffer || st->xhdr.stk == NULL)
     return old_header;
 
@@ -722,13 +721,15 @@ write_extended (bool global, struct tar_stat_info *st, 
union block *old_header)
     {
       type = XGLTYPE;
       p = xheader_ghdr_name ();
+      time (&t);
     }
   else
     {
       type = XHDTYPE;
       p = xheader_xhdr_name (st);
+      t = st->stat.st_mtime;
     }
-  xheader_write (type, p, &st->xhdr);
+  xheader_write (type, p, t, &st->xhdr);
   free (p);
   header = find_next_block ();
   memcpy (header, &hp.buffer, sizeof (hp.buffer));
diff --git a/src/xheader.c b/src/xheader.c
index c779c0a..e8fd6a2 100644
--- a/src/xheader.c
+++ b/src/xheader.c
@@ -364,14 +364,14 @@ xheader_ghdr_name (void)
 }
 
 void
-xheader_write (char type, char *name, struct xheader *xhdr)
+xheader_write (char type, char *name, time_t t, struct xheader *xhdr)
 {
   union block *header;
   size_t size;
   char *p;
 
   size = xhdr->size;
-  header = start_private_header (name, size);
+  header = start_private_header (name, size, t);
   header->header.typeflag = type;
 
   simple_finish_header (header);
@@ -413,7 +413,7 @@ xheader_write_global (struct xheader *xhdr)
   for (kp = keyword_global_override_list; kp; kp = kp->next)
     code_string (kp->value, kp->pattern, xhdr);
   xheader_finish (xhdr);
-  xheader_write (XGLTYPE, name = xheader_ghdr_name (), xhdr);
+  xheader_write (XGLTYPE, name = xheader_ghdr_name (), time (NULL), xhdr);
   free (name);
 }
 
-- 
1.6.4.2

>From 63e092548a9b87c0be0f0b286c883e1f3d52294c Mon Sep 17 00:00:00 2001
From: Sergey Poznyakoff <address@hidden>
Date: Wed, 7 Oct 2009 21:08:29 +0300
Subject: [PATCH 3/3] Provide a way to explicitly set mtime for extended header 
ustar blocks.

* src/tar.c (struct textual_date): ts is a copy of the structure,
not a pointer to it. Date is a copy as well, hence the `const' is
taken away.
(get_date_or_file): Return 0/1 depending on success/failure.
Copy timestamp to the `ts' member. Store a copy of the string
in `date'.
(report_textual_dates): Report only if verbose_option is set,
but always free the list.
(expand_pax_option): New function.
(parse_opt): Preprocess the argument to xheader_set_option with
expand_pax_option.
(decode_options): Call report_textual_dates unconditionally.
* src/xheader.c (exthdr_mtime_option, exthdr_mtime)
(globexthdr_mtime_option, globexthdr_mtime): New statics.
(xheader_set_keyword_equal): handle exthdr.mtime and globexthdr.mtime.
(xheader_write): Override `t' argument if a corresponding
exthdr.mtime or globexthdr.mtime option is set.
* NEWS: Update
* doc/tar.texi: Document the changes.
---
 NEWS          |   34 +++++++++++++++++++++-
 doc/tar.texi  |   46 +++++++++++++++++++++++++++--
 src/tar.c     |   89 ++++++++++++++++++++++++++++++++++++++++++++++++--------
 src/xheader.c |   37 +++++++++++++++++++++++
 4 files changed, 189 insertions(+), 17 deletions(-)

diff --git a/NEWS b/NEWS
index bf02a24..7624d80 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-GNU tar NEWS - User visible changes. 2009-09-08
+GNU tar NEWS - User visible changes. 2009-10-07
 Please send GNU tar bug reports to <address@hidden>
 
 
@@ -53,11 +53,43 @@ and sets the exit code to 1, which means "some files 
differ".
 If the --warning=no-file-removed option is given, no warning
 is issued and the exit code remains 0.
 
+* Modification times of PAX extended headers.
+
+Modification times in the ustar header blocks for the 
+extended headers are set to the mtimes of the corresponding archive
+members.  This can be overridden by the
+
+  --pax-opion='exthdr.mtime=STRING'
+
+command line option.  The STRING is either the number of seconds since
+the Epoch or a `Time reference' (see below).
+
+Modification times in the ustar header blocks for the global
+extended headers are set to the time when tar was invoked.
+
+This can be overridden by the
+
+  --pax-opion='globexthdr.mtime=STRING'
+
+command line option.  The STRING is either the number of seconds since
+the Epoch or a `Time reference' (see below).
+
+* Time references in --pax-option argument.
+
+Any value from the --pax-option argument that is enclosed in a pair
+of curly braces.  In that case, the string between the braces is
+understood either as a textual time representation, as described in
+chapter 7, "Date input formats", of the Tar manual, or as a name of
+the existing file, starting with `/' or `.'.  In the latter
+case, the value is replaced with the modification time of that file.
+
 * Bugfixes
 ** Fix handling of hard link targets by -c --transform.
 ** Fix hard links recognition with -c --remove-files.
 ** Fix restoring files from backup (debian bug #508199).
 ** Correctly restore modes and permissions on existing directories.
+** The --remove-files option removes the files only if they were
+succesfully stored in the archive.
 
 
 version 1.22 - Sergey Poznyakoff, 2009-03-05
diff --git a/doc/tar.texi b/doc/tar.texi
index e609368..1102e32 100644
--- a/doc/tar.texi
+++ b/doc/tar.texi
@@ -3055,8 +3055,8 @@ This option does not affect extraction from archives.
 
 @opsummary{pax-option}
 @item address@hidden
-This option is meaningful only with @acronym{POSIX.1-2001} archives
-(@pxref{posix}).  It modifies the way @command{tar} handles the
+This option enables creation of the archive in @acronym{POSIX.1-2001}
+format (@pxref{posix}) and modifies the way @command{tar} handles the
 extended header keywords.  @var{Keyword-list} is a comma-separated
 list of keyword options.  @xref{PAX keywords}, for a detailed
 discussion.
@@ -5400,7 +5400,7 @@ Name of the file owner group.
 @vrindex TAR_ATIME, to-command environment
 @item TAR_ATIME
 Time of last access. It is a decimal number, representing seconds
-since the epoch.  If the archive provides times with nanosecond
+since the Epoch.  If the archive provides times with nanosecond
 precision, the nanoseconds are appended to the timestamp after a
 decimal point.
 
@@ -9409,6 +9409,13 @@ will use the following default value:
 %d/PaxHeaders.%p/%f
 @end smallexample
 
address@hidden address@hidden
+
+This keyword defines the value of the @samp{mtime} field that
+is written into the ustar header blocks for the extended headers.
+By default, the @samp{mtime} field is set to the modification time
+of the archive member described by that extended headers.
+
 @item address@hidden
 This keyword allows user control over the name that is written into
 the ustar header blocks for global extended header records.  The name
@@ -9438,6 +9445,13 @@ where @samp{$TMPDIR} represents the value of the 
@var{TMPDIR}
 environment variable.  If @var{TMPDIR} is not set, @command{tar}
 uses @samp{/tmp}.
 
address@hidden address@hidden
+
+This keyword defines the value of the @samp{mtime} field that
+is written into the ustar header blocks for the global extended headers.
+By default, the @samp{mtime} field is set to the time when
address@hidden was invoked.
+
 @item @address@hidden
 When used with one of archive-creation commands, these keyword/value pairs
 will be included at the beginning of the archive in a global extended
@@ -9467,6 +9481,32 @@ the group name will be forced to a new value for all 
files
 stored in the archive.
 @end table
 
+In any of the forms described above, the @var{value} may be
+a string enclosed in curly braces.  In that case, the string
+between the braces is understood either as a textual time
+representation, as described in @ref{Date input formats}, or a name of
+the existing file, starting with @samp{/} or @samp{.}.  In the latter
+case, the modification time of that file is used.
+
+For example, to set all modification times to the current date, you
+use the following option:
+
address@hidden
+--pax-option='mtime:address@hidden@}'
address@hidden smallexample
+
+Note quoting of the option's argument.
+
address@hidden archives, binary equivalent
address@hidden binary equivalent archives, creating
+As another example, here is the option that ensures that any two
+archives created using it, will be binary equivalent if they have the
+same contents:
+
address@hidden
+--pax-option=exthdr.name=%d/PaxHeaders/%f,atime:=0
address@hidden smallexample
+
 @node Checksumming
 @subsection Checksumming Problems
 
diff --git a/src/tar.c b/src/tar.c
index 0c59352..a639974 100644
--- a/src/tar.c
+++ b/src/tar.c
@@ -1002,12 +1002,12 @@ set_stat_signal (const char *name)
 struct textual_date
 {
   struct textual_date *next;
-  struct timespec *ts;
+  struct timespec ts;
   const char *option;
-  const char *date;
+  char *date;
 };
 
-static void
+static int
 get_date_or_file (struct tar_args *args, const char *option,
                  const char *str, struct timespec *ts)
 {
@@ -1030,17 +1030,19 @@ get_date_or_file (struct tar_args *args, const char 
*option,
          WARN ((0, 0, _("Substituting %s for unknown date format %s"),
                 tartime (*ts, false), quote (str)));
          ts->tv_nsec = 0;
+         return 1;
        }
       else
        {
          struct textual_date *p = xmalloc (sizeof (*p));
-         p->ts = ts;
+         p->ts = *ts;
          p->option = option;
-         p->date = str;
+         p->date = xstrdup (str);
          p->next = args->textual_date;
          args->textual_date = p;
        }
     }
+  return 0;
 }
 
 static void
@@ -1050,10 +1052,14 @@ report_textual_dates (struct tar_args *args)
   for (p = args->textual_date; p; )
     {
       struct textual_date *next = p->next;
-      char const *treated_as = tartime (*p->ts, true);
-      if (strcmp (p->date, treated_as) != 0)
-       WARN ((0, 0, _("Option %s: Treating date `%s' as %s"),
-              p->option, p->date, treated_as));
+      if (verbose_option)
+       {
+         char const *treated_as = tartime (p->ts, true);
+         if (strcmp (p->date, treated_as) != 0)
+           WARN ((0, 0, _("Option %s: Treating date `%s' as %s"),
+                  p->option, p->date, treated_as));
+       }
+      free (p->date);
       free (p);
       p = next;
     }
@@ -1273,6 +1279,60 @@ tar_help_filter (int key, const char *text, void *input)
   return s;
 }
 
+static char *
+expand_pax_option (struct tar_args *targs, const char *arg)
+{
+  struct obstack stk;
+  char *res;
+  
+  obstack_init (&stk);
+  while (*arg)
+    {
+      size_t seglen = strcspn (arg, ",");
+      char *p = memchr (arg, '=', seglen);
+      if (p)
+       {
+         size_t len = p - arg + 1;
+         obstack_grow (&stk, arg, len);
+         len = seglen - len;
+         for (++p; *p && isspace ((unsigned char) *p); p++)
+           len--;
+         if (*p == '{' && p[len-1] == '}')
+           {
+             struct timespec ts;
+             char *tmp = xmalloc (len);
+             memcpy (tmp, p + 1, len-2);
+             tmp[len-2] = 0;
+             if (get_date_or_file (targs, "--pax-option", tmp, &ts) == 0)
+               {
+                 char buf[UINTMAX_STRSIZE_BOUND], *s;
+                 s = umaxtostr (ts.tv_sec, buf);
+                 obstack_grow (&stk, s, strlen (s));
+               }
+             else
+               obstack_grow (&stk, p, len);
+             free (tmp);
+           }
+         else
+           obstack_grow (&stk, p, len);
+       }
+      else
+       obstack_grow (&stk, arg, seglen);
+
+      arg += seglen;
+      if (*arg)
+       {
+         obstack_1grow (&stk, *arg);
+         arg++;
+       }
+    }
+  obstack_1grow (&stk, 0);
+  res = xstrdup (obstack_finish (&stk));
+  obstack_free (&stk, NULL);
+  return res;
+}
+
+
 static error_t
 parse_opt (int key, char *arg, struct argp_state *state)
 {
@@ -1840,8 +1900,12 @@ parse_opt (int key, char *arg, struct argp_state *state)
       break;
       
     case PAX_OPTION:
-      args->pax_option = true;
-      xheader_set_option (arg);
+      {
+       char *tmp = expand_pax_option (args, arg);
+       args->pax_option = true;
+       xheader_set_option (tmp);
+       free (tmp);
+      }
       break;
       
     case POSIX_OPTION:
@@ -2440,8 +2504,7 @@ decode_options (int argc, char **argv)
 
   checkpoint_finish_compile ();
   
-  if (verbose_option)
-    report_textual_dates (&args);
+  report_textual_dates (&args);
 }
 
 
diff --git a/src/xheader.c b/src/xheader.c
index e8fd6a2..5eabdfb 100644
--- a/src/xheader.c
+++ b/src/xheader.c
@@ -96,9 +96,15 @@ static struct keyword_list *global_header_override_list;
 /* Template for the name field of an 'x' type header */
 static char *exthdr_name;
 
+static char *exthdr_mtime_option;
+static time_t exthdr_mtime;
+
 /* Template for the name field of a 'g' type header */
 static char *globexthdr_name;
 
+static char *globexthdr_mtime_option;
+static time_t globexthdr_mtime;
+
 bool
 xheader_keyword_deleted_p (const char *kw)
 {
@@ -157,6 +163,21 @@ xheader_set_single_keyword (char *kw)
 }
 
 static void
+assign_time_option (char **sval, time_t *tval, const char *input)
+{
+  uintmax_t u;
+  char *p;
+  time_t t = u = strtoumax (input, &p, 10);
+  if (t != u || *p || errno == ERANGE)
+    ERROR ((0, 0, _("Time stamp is out of allowed range")));
+  else
+    {
+      *tval = t;
+      assign_string (sval, input);
+    }
+}
+
+static void
 xheader_set_keyword_equal (char *kw, char *eq)
 {
   bool global = true;
@@ -186,6 +207,10 @@ xheader_set_keyword_equal (char *kw, char *eq)
     assign_string (&exthdr_name, p);
   else if (strcmp (kw, "globexthdr.name") == 0)
     assign_string (&globexthdr_name, p);
+  else if (strcmp (kw, "exthdr.mtime") == 0)
+    assign_time_option (&exthdr_mtime_option, &exthdr_mtime, p);
+  else if (strcmp (kw, "globexthdr.mtime") == 0)
+    assign_time_option (&globexthdr_mtime_option, &globexthdr_mtime, p);
   else
     {
       if (xheader_protected_keyword_p (kw))
@@ -371,6 +396,18 @@ xheader_write (char type, char *name, time_t t, struct 
xheader *xhdr)
   char *p;
 
   size = xhdr->size;
+  switch (type)
+    {
+    case XGLTYPE:
+      if (globexthdr_mtime_option)
+       t = globexthdr_mtime;
+      break;
+
+    case XHDTYPE:
+      if (exthdr_mtime_option)
+       t = exthdr_mtime;
+      break;
+    }
   header = start_private_header (name, size, t);
   header->header.typeflag = type;
 
-- 
1.6.4.2


reply via email to

[Prev in Thread] Current Thread [Next in Thread]