mtime in x header

View: New views
9 Messages — Rating Filter:   Alert me  

mtime in x header

by Michael D. Adams :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Running tar over the exact same file at two different time isn't
producing the exact same tar file.

I've tracked this down to the mtime field in the header that has
typeflag=x.  Specifically the mtime field is storing modified time of
the *tar* file and not the mtime of the files inside the tar.  Below
is an example showing this happening.

Questions:
 (1) Is this behavior in conformance with the POSIX-2001 spec?  I
haven't seen anywhere that says what the mtime field should be when
there is an mtime extended header, but other tar/pax implementations
just fill it with zeros.  (If the spec doesn't specify either way then
maybe it isn't technically a bug.)

 (2) Is there a way to work around this?  It is causing havoc with my
backups.  I had been using "tar" as a cheap way to serialize all the
info about a file (e.g. ctime, owner, acls, etc.).  However if "tar"
embeds the time that the tar command *ran* in the tar file, then it
will generate all sorts of spurious differences.

For example:

$ tar --version # I've also tested this with 1.22 with the same results
tar (GNU tar) 1.15.1
$
$ echo test >test.txt
$ sleep 5
$
$ tar --pax-option=exthdr.name=%d/PaxHeaders/%f -cf test1.tar test.txt
$ sleep 5
$
$ tar --pax-option=exthdr.name=%d/PaxHeaders/%f -cf test2.tar test.txt
$
$ printf '%o\n' `stat --format=%Y test.txt` # Print modification time
in octal (the format "tar" uses)
11260565716
$ printf '%o\n' `stat --format=%Y test1.tar` # Print modification time
in octal (the format "tar" uses)
11260565726
$
$ printf '%o\n' `stat --format=%Y test2.tar` # Print modification time
in octal (the format "tar" uses)
11260565740
$
$ diff test1.tar test2.tar
Binary files test1.tar and test2.tar differ
$ hexdump -C test1.tar >test1.tar.hex
$ hexdump -C test2.tar >test2.tar.hex
$ diff -u test1.tar.hex test2.tar.hex
--- test1.tar.hex       2009-09-30 01:25:57.000000000 -0400
+++ test2.tar.hex       2009-09-30 01:26:01.000000000 -0400
@@ -5,7 +5,7 @@
 00000060  00 00 00 00 30 30 30 30  36 34 34 00 30 30 32 33  |....0000644.0023|
 00000070  35 31 32 00 30 30 30 37  36 34 32 00 30 30 30 30  |512.0007642.0000|
 00000080  30 30 30 30 30 37 34 00  31 31 32 36 30 35 36 35  |0000074.11260565|
-00000090  37 32 36 00 30 31 33 33  33 30 00 20 78 00 00 00  |726.013330. x...|
+00000090  37 34 30 00 30 31 33 33  32 34 00 20 78 00 00 00  |740.013324. x...|
 000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
 *
 00000100  00 75 73 74 61 72 00 30  30 00 00 00 00 00 00 00  |.ustar.00.......|



Re: mtime in x header

by Tim Kientzle :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Michael D. Adams wrote:

> Running tar over the exact same file at two different time isn't
> producing the exact same tar file.
>
> I've tracked this down to the mtime field in the header that has
> typeflag=x.
>
> Questions:
>  (1) Is this behavior in conformance with the POSIX-2001 spec?  I
> haven't seen anywhere that says what the mtime field should be when
> there is an mtime extended header, but other tar/pax implementations
> just fill it with zeros.

I don't think the POSIX spec says anything about this.
You should be able to avoid it, of course, by using
"ustar" format, which does not generate the 'x' entries.

I'm curious which "other tar/pax" implementations
you surveyed?

bsdtar actually uses the same mtime value for
the header of the 'x' entry that it uses for
the regular header.

Cheers,

Tim




Re: mtime in x header

by Michael D. Adams :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Wed, Sep 30, 2009 at 11:01 PM, Tim Kientzle <kientzle@...> wrote:

> Michael D. Adams wrote:
>>
>> Running tar over the exact same file at two different time isn't
>> producing the exact same tar file.
>>
>> I've tracked this down to the mtime field in the header that has
>> typeflag=x.
>>
>> Questions:
>>  (1) Is this behavior in conformance with the POSIX-2001 spec?  I
>> haven't seen anywhere that says what the mtime field should be when
>> there is an mtime extended header, but other tar/pax implementations
>> just fill it with zeros.
>
> I don't think the POSIX spec says anything about this.
> You should be able to avoid it, of course, by using
> "ustar" format, which does not generate the 'x' entries.

Except that I was going to use the extended headers to store ACL
information.  But if push comes to shove I might just use your
suggestion and leave out those ACLs so I can store it in "ustar"
format.

> I'm curious which "other tar/pax" implementations
> you surveyed?

Schilling's star uses the mtime of the file inside the tar (like you
say that bsdtar does), but Schilling's spax uses zeros.  (Version
"1.5a75 (i386-redhat-linux-gnu)" on both.)  Either behavior would be
fine for my purposes.  Only the way gnutar does it causes a problem.

Michael D. Adams
mdmkolbe@...



Re: mtime in x header

by Sergey Poznyakoff-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Hi Michael,

>  (1) Is this behavior in conformance with the POSIX-2001 spec?

As far as I can tell, yes, it is. POSIX says that the fields of
a pax extended header block, other than its size, are not
meaningful and should be selected such as to provide reasonable
file access and times for the case when the archive is read by
a tar utility not aware of pax extensions, in which case extended
blocks will be extracted as regular files.

>  (2) Is there a way to work around this?

Try the attached patch.  It introduces the exthdr.mtime argument
to the --pax-option.  Its value is either a time in one of the
formats described in [1] or a full path name to the reference file
(just as with the --mtime option). For example, the following invocation
should create binary equivalent archives:

tar \
 --pax-option=exthdr.name=%d/PaxHeaders/%f \
 --pax-option=globexthdr.name=GlobalHead,exthdr.mtime='1 jan 1970',atime:=0 \
 -c -f test.tar test.txt

Regards,
Sergey

[1] http://www.gnu.org/software/tar/manual/html_node/absolute.html


diff --git a/src/common.h b/src/common.h
index 73865ec..e0137af 100644
--- a/src/common.h
+++ b/src/common.h
@@ -453,7 +453,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,
@@ -703,6 +703,10 @@ const char *archive_format_string (enum archive_format fmt);
 const char *subcommand_string (enum subcommand c);
 void set_exit_status (int val);
 
+struct tar_args;
+void get_date_or_file (struct tar_args *args, const char *option,
+       const char *str, struct timespec *ts);
+
 /* Module update.c.  */
 
 extern char *output_start;
@@ -723,7 +727,7 @@ void xheader_finish (struct xheader *hdr);
 void xheader_destroy (struct xheader *hdr);
 char *xheader_xhdr_name (struct tar_stat_info *st);
 char *xheader_ghdr_name (void);
-void xheader_set_option (char *string);
+void xheader_set_option (char *string, struct tar_args *args);
 void xheader_string_begin (struct xheader *xhdr);
 void xheader_string_add (struct xheader *xhdr, char const *s);
 bool xheader_string_end (struct xheader *xhdr, char const *keyword);
diff --git a/src/create.c b/src/create.c
index 6f3113e..a3a08f0 100644
--- a/src/create.c
+++ b/src/create.c
@@ -516,9 +516,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));
@@ -526,7 +525,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,8 +561,10 @@ write_gnu_long_link (struct tar_stat_info *st, const char *p, char type)
   size_t bufsize;
   union block *header;
   char *tmpname;
-
-  header = start_private_header ("././@LongLink", size);
+  time_t t;
+  
+  time (&t);
+  header = start_private_header ("././@LongLink", size, t);
   FILL(header->header.mtime, '0');
   FILL(header->header.mode, '0');
   FILL(header->header.uid, '0');
diff --git a/src/tar.c b/src/tar.c
index e3300fb..d73fc35 100644
--- a/src/tar.c
+++ b/src/tar.c
@@ -1007,7 +1007,7 @@ struct textual_date
   const char *date;
 };
 
-static void
+void
 get_date_or_file (struct tar_args *args, const char *option,
   const char *str, struct timespec *ts)
 {
@@ -1841,7 +1841,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
       
     case PAX_OPTION:
       args->pax_option = true;
-      xheader_set_option (arg);
+      xheader_set_option (arg, args);
       break;
       
     case POSIX_OPTION:
diff --git a/src/xheader.c b/src/xheader.c
index c779c0a..e292f50 100644
--- a/src/xheader.c
+++ b/src/xheader.c
@@ -25,6 +25,8 @@
 
 #include "common.h"
 
+struct timespec exthdr_mtime_option;
+
 static bool xheader_protected_pattern_p (char const *pattern);
 static bool xheader_protected_keyword_p (char const *keyword);
 static void xheader_set_single_keyword (char *) __attribute__ ((noreturn));
@@ -157,7 +159,7 @@ xheader_set_single_keyword (char *kw)
 }
 
 static void
-xheader_set_keyword_equal (char *kw, char *eq)
+xheader_set_keyword_equal (char *kw, char *eq, struct tar_args *tar_args)
 {
   bool global = true;
   char *p = eq;
@@ -184,6 +186,9 @@ xheader_set_keyword_equal (char *kw, char *eq)
     }
   else if (strcmp (kw, "exthdr.name") == 0)
     assign_string (&exthdr_name, p);
+  else if (strcmp (kw, "exthdr.mtime") == 0)
+    get_date_or_file (tar_args,
+      "--pax-option=exthdr.mtime", p, &exthdr_mtime_option);
   else if (strcmp (kw, "globexthdr.name") == 0)
     assign_string (&globexthdr_name, p);
   else
@@ -198,7 +203,7 @@ xheader_set_keyword_equal (char *kw, char *eq)
 }
 
 void
-xheader_set_option (char *string)
+xheader_set_option (char *string, struct tar_args *tar_args)
 {
   char *token;
   for (token = strtok (string, ","); token; token = strtok (NULL, ","))
@@ -207,7 +212,7 @@ xheader_set_option (char *string)
       if (!p)
  xheader_set_single_keyword (token);
       else
- xheader_set_keyword_equal (token, p);
+ xheader_set_keyword_equal (token, p, tar_args);
     }
 }
 
@@ -369,9 +374,14 @@ xheader_write (char type, char *name, struct xheader *xhdr)
   union block *header;
   size_t size;
   char *p;
-
+  time_t t;
+  
   size = xhdr->size;
-  header = start_private_header (name, size);
+  if (exthdr_mtime_option.tv_sec)
+    t = exthdr_mtime_option.tv_sec;
+  else
+    time (&t);
+  header = start_private_header (name, size, t);
   header->header.typeflag = type;
 
   simple_finish_header (header);

Re: mtime in x header

by Michael D. Adams :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Thu, Oct 1, 2009 at 3:46 AM, Sergey Poznyakoff <gray@...> wrote:

> Hi Michael,
>
>>  (1) Is this behavior in conformance with the POSIX-2001 spec?
>
> As far as I can tell, yes, it is. POSIX says that the fields of
> a pax extended header block, other than its size, are not
> meaningful and should be selected such as to provide reasonable
> file access and times for the case when the archive is read by
> a tar utility not aware of pax extensions, in which case extended
> blocks will be extracted as regular files.

For 'x' headers, I would think the contained file's mtime would be
more "meaningful" than the tar file's creation time.  Though for 'g'
headers, I could see the tar file's modification time being more
"meaningful".  (I haven't tested 'g' headers.  I haven't figured out a
way to get tar to generate them.  (Which is fine, since my application
doesn't need them.))

(BTW, that probably means exthdr.mtime should also have an analogous
globexthdr.mtime option.)

>>  (2) Is there a way to work around this?
>
> Try the attached patch.  It introduces the exthdr.mtime argument
> to the --pax-option.  Its value is either a time in one of the
> formats described in [1] or a full path name to the reference file
> (just as with the --mtime option). For example, the following invocation
> should create binary equivalent archives:
>
> tar \
>  --pax-option=exthdr.name=%d/PaxHeaders/%f \
>  --pax-option=globexthdr.name=GlobalHead,exthdr.mtime='1 jan 1970',atime:=0 \
>  -c -f test.tar test.txt

Your patch works but has a bug.  If I set the time to '@1' or any
non-zero value it works, but if I set it to '@0' or '1970-1-1
00:00:00Z', then it reverts back to placing the tar file's creation
time in there.  (Note plain '1970-1-1 00:00:00' without the 'Z' is
local time and thus not zero.)

In xheader_write, I note that "exthdr_mtime_option.tv_sec" is being
checked to see if it is non-zero to determine if it has already been
set.  Of course when we use '@0', it will be zero so the function
assumes it hasn't been set.  Also I don't think C (at least prior to
C99) will always initialize exthdr_mtime to zero (compilers and OS may
vary).  (Then again maybe the tar sources are already assuming C99.)
The solution to all this is probably to have a variable next to
exthdr_mtime_option that signals whether it has been set and to
explicitly initialize that flag to false.

Otherwise, it seems to work great.

Michael D. Adams



Re: mtime in x header

by Michael D. Adams :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Thu, Oct 1, 2009 at 1:03 PM, Michael D. Adams <mdmkolbe@...> wrote:
> Also I don't think C (at least prior to
> C99) will always initialize exthdr_mtime to zero (compilers and OS may
> vary).  (Then again maybe the tar sources are already assuming C99.)

Correction: I was wrong, both C99 and C90 require that global
variables get initialized to zero.



Re: mtime in x header

by Sergey Poznyakoff-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Michael D. Adams <mdmkolbe@...> ha escrit:

> (I haven't tested 'g' headers.  I haven't figured out a
> way to get tar to generate them.

A global header is created if you supply at least one global keyword,
e.g.:

   tar --pax-option 'comment=Test archive' -c -f ...

(global keywords are defined using `keyword=value' syntax, as
opposed to `keyword:=value', which defines per-member keywords).

> Your patch works but has a bug.  If I set the time to '@1' or any
> non-zero value it works, but if I set it to '@0' or '1970-1-1
> 00:00:00Z', then it reverts back to placing the tar file's creation
> time in there.

Yes, of course: it was intended as a proof of concept; in the final
version I will definitely not rely on comparison of the timestamp to 0
to decide if it was set.

> Also I don't think C (at least prior to
> C99) will always initialize exthdr_mtime to zero (compilers and OS may
> vary).

Even the very first C compiler would do it: the memory allocated for BSS
was always initialized to zeroes, since the PDP times.

Regards,
Sergey



Re: mtime in x header

by Sergey Poznyakoff-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

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},exthdr.mtime={@0}'

Thanks & regards,
Sergey


From 7cb84c25ee51f443c69443e217efe194d12678ea Mon Sep 17 00:00:00 2001
From: Sergey Poznyakoff <gray@...>
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 <mdmkolbe@...>.

* 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 <gray@...>
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 <bug-tar@...>
 
 
@@ -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 --pax-option=@var{keyword-list}
-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
 
+@item exthdr.mtime=@var{value}
+
+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 globexthdr.name=@var{string}
 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}.
 
+@item exthdr.mtime=@var{value}
+
+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
+@command{tar} was invoked.
+
 @item @var{keyword}=@var{value}
 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:
+
+@smallexample
+--pax-option='mtime:=@{now@}'
+@end smallexample
+
+Note quoting of the option's argument.
+
+@cindex archives, binary equivalent
+@cindex 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:
+
+@smallexample
+--pax-option=exthdr.name=%d/PaxHeaders/%f,atime:=0
+@end 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


Re: mtime in x header

by Michael D. Adams :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Thanks.  I've compiled the git version and it seems to meet my needs.

On Wed, Oct 7, 2009 at 2:23 PM, Sergey Poznyakoff <gray@...> wrote:

> 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},exthdr.mtime={@0}'
>
> Thanks & regards,
> Sergey
>
>
> From 7cb84c25ee51f443c69443e217efe194d12678ea Mon Sep 17 00:00:00 2001
> From: Sergey Poznyakoff <gray@...>
> 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 <mdmkolbe@...>.
>
> * 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 <gray@...>
> 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 <bug-tar@...>
>
>
> @@ -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 --pax-option=@var{keyword-list}
> -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
>
> +@item exthdr.mtime=@var{value}
> +
> +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 globexthdr.name=@var{string}
>  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}.
>
> +@item exthdr.mtime=@var{value}
> +
> +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
> +@command{tar} was invoked.
> +
>  @item @var{keyword}=@var{value}
>  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:
> +
> +@smallexample
> +--pax-option='mtime:=@{now@}'
> +@end smallexample
> +
> +Note quoting of the option's argument.
> +
> +@cindex archives, binary equivalent
> +@cindex 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:
> +
> +@smallexample
> +--pax-option=exthdr.name=%d/PaxHeaders/%f,atime:=0
> +@end 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
>
>
>