Преглед на файлове

New options: --owner-map and --group-map.

* NEWS: Update.
* doc/tar.1: Document --owner-map and --group-map
* doc/tar.texi: Likewise.

* src/map.c: New file.
* src/Makefile.am: Add map.c
* src/common.h (owner_map_read, owner_map_translate)
(group_map_read, group_map_translate): New protos.
* src/create.c (start_header): Use owner_map_translate
and group_map_translate to optionally translate user/group names/ids.
* src/tar.c: New options --owner-map and --group-map.

* tests/map.at: New file.
* tests/Makefile.am: Add map.at
* tests/testsuite.at: Include map.at.
Sergey Poznyakoff преди 9 години
родител
ревизия
1a615a41f5
променени са 11 файла, в които са добавени 550 реда и са изтрити 16 реда
  1. 19 1
      NEWS
  2. 55 5
      doc/tar.1
  3. 89 1
      doc/tar.texi
  4. 1 0
      src/Makefile.am
  5. 7 0
      src/common.h
  6. 9 9
      src/create.c
  7. 283 0
      src/map.c
  8. 14 0
      src/tar.c
  9. 1 0
      tests/Makefile.am
  10. 71 0
      tests/map.at
  11. 1 0
      tests/testsuite.at

+ 19 - 1
NEWS

@@ -1,4 +1,4 @@
-GNU tar NEWS - User visible changes. 2015-08-03
+GNU tar NEWS - User visible changes. 2015-11-02
 Please send GNU tar bug reports to <[email protected]>
 Please send GNU tar bug reports to <[email protected]>
 
 
 
 
@@ -22,6 +22,24 @@ This option affects all --files-from options that occur after it in
 the command line.  Its effect is reverted by the
 the command line.  Its effect is reverted by the
 --no-verbatim-files-from option.
 --no-verbatim-files-from option.
 
 
+* New options: --owner-map=FILE and --group-map=FILE
+
+These two options provide fine-grained control over what user/group
+names (or IDs) should be mapped when adding files to archive.
+
+For both options, FILE is a plain text file with user or group
+mappings.  Empty lines are ignored.  Comments are introduced with
+# sign (unless quoted) and extend to the end of the corresponding
+line.  Each non-empty line defines translation for a single UID (GID).
+It must consist of two fields, delimited by any amount of whitespace:
+
+     OLDNAME NEWNAME[:NEWID]
+
+OLDNAME is either a valid user (group) name or a ID prefixed with +.  Unless
+NEWID is supplied, NEWNAME must also be either a valid name or a
++ID.  Otherwise, both NEWNAME and NEWID need not be listed in the
+system user database.
+
 * --null option reads file names verbatim
 * --null option reads file names verbatim
 
 
 The --null option implies --verbatim-files-from.  I.e. each line 
 The --null option implies --verbatim-files-from.  I.e. each line 

+ 55 - 5
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 "August 24, 2015" "TAR" "GNU TAR Manual"
+.TH TAR 1 "November 2, 2015" "TAR" "GNU TAR Manual"
 .SH NAME
 .SH NAME
 tar \- an archiving utility
 tar \- an archiving utility
 .SH SYNOPSIS
 .SH SYNOPSIS
@@ -467,8 +467,33 @@ Delay setting modification times and permissions of extracted
 directories until the end of extraction.  Use this option when
 directories until the end of extraction.  Use this option when
 extracting from an archive which has unusual member ordering.
 extracting from an archive which has unusual member ordering.
 .TP
 .TP
-\fB\-\-group\fR=\fINAME\fR
-Force \fINAME\fR as group for added files.
+\fB\-\-group\fR=\fINAME\fR[:\fIGID\fR]
+Force \fINAME\fR as group for added files.  If \fIGID\fR is not
+supplied, \fINAME\fR can be either a user name or numeric GID.  In
+this case the missing part (GID or name) will be inferred from the
+current host's group database.
+
+When used with \fB\-\-group\-map\fR=\fIFILE\fR, affects only those
+files whose owner group is not listed in \fIFILE\fR.
+.TP
+\fB\-\-group\-map\fR=\fIFILE\fR
+Read group translation map from \fIFILE\fR.  Empty lines are ignored.
+Comments are introduced with \fB#\fR sign and extend to the end of line.
+Each non-empty line in \fIFILE\fR defines translation for a single
+group.  It must consist of two fields, delimited by any amount of whitespace:
+
+.EX
+\fIOLDGRP\fR \fINEWGRP\fR[\fB:\fINEWGID\fR]
+.EE
+
+\fIOLDGRP\fR is either a valid group name or a GID prefixed with
+\fB+\fR.  Unless \fINEWGID\fR is supplied, \fINEWGRP\fR must also be
+either a valid group name or a \fB+\fIGID\fR.  Otherwise, both
+\fINEWGRP\fR and \fINEWGID\fR need not be listed in the system group
+database.
+
+As a result, each input file with owner group \fIOLDGRP\fR will be
+stored in archive with owner group \fINEWGRP\fR and GID \fINEWGID\fR.
 .TP
 .TP
 \fB\-\-mode\fR=\fICHANGES\fR
 \fB\-\-mode\fR=\fICHANGES\fR
 Force symbolic mode \fICHANGES\fR for added files.
 Force symbolic mode \fICHANGES\fR for added files.
@@ -494,8 +519,33 @@ Apply the user's umask when extracting permissions from the archive
 \fB\-\-numeric\-owner\fR
 \fB\-\-numeric\-owner\fR
 Always use numbers for user/group names.
 Always use numbers for user/group names.
 .TP
 .TP
-\fB\-\-owner\fR=\fINAME\fR
-Force \fINAME\fR as owner for added files.
+\fB\-\-owner\fR=\fINAME\fR[:\fIUID\fR]
+Force \fINAME\fR as owner for added files.  If \fIUID\fR is not
+supplied, \fINAME\fR can be either a user name or numeric UID.  In
+this case the missing part (UID or name) will be inferred from the
+current host's user database.
+
+When used with \fB\-\-owner\-map\fR=\fIFILE\fR, affects only those
+files whose owner is not listed in \fIFILE\fR.
+.TP
+\fB\-\-owner\-map\fR=\fIFILE\fR
+Read owner translation map from \fIFILE\fR.  Empty lines are ignored.
+Comments are introduced with \fB#\fR sign and extend to the end of line.
+Each non-empty line in \fIFILE\fR defines translation for a single
+UID.  It must consist of two fields, delimited by any amount of whitespace:
+
+.EX
+\fIOLDUSR\fR \fINEWUSR\fR[\fB:\fINEWUID\fR]
+.EE
+
+\fIOLDUSR\fR is either a valid user name or a UID prefixed with
+\fB+\fR.  Unless \fINEWUID\fR is supplied, \fINEWUSR\fR must also be
+either a valid user name or a \fB+\fIUID\fR.  Otherwise, both
+\fINEWUSR\fR and \fINEWUID\fR need not be listed in the system user
+database.
+
+As a result, each input file owned by \fIOLDUSR\fR will be
+stored in archive with owner name \fINEWUSR\fR and UID \fINEWUID\fR.
 .TP
 .TP
 \fB\-p\fR, \fB\-\-preserve\-permissions\fR, \fB\-\-same\-permissions\fR
 \fB\-p\fR, \fB\-\-preserve\-permissions\fR, \fB\-\-same\-permissions\fR
 extract information about file permissions (default for superuser)
 extract information about file permissions (default for superuser)

+ 89 - 1
doc/tar.texi

@@ -2742,7 +2742,19 @@ rather than the group from the source file.  @var{group} can specify a
 symbolic name, or a numeric @acronym{ID}, or both as
 symbolic name, or a numeric @acronym{ID}, or both as
 @var{name}:@var{id}.  @xref{override}.
 @var{name}:@var{id}.  @xref{override}.
 
 
-Also see the comments for the @option{--owner=@var{user}} option.
+Also see the @option{--group-map} option and comments for the
+@option{--owner=@var{user}} option.
+
+@opsummary{group-map}
+@item --group-map=@var{file}
+
+Read owner group translation map from @var{file}.  This option allows to
+translate only certain group names and/or UIDs.  @xref{override}, for a
+detailed description.  When used together with @option{--group}
+option, the latter affects only those files whose owner group is not listed
+in the @var{file}.
+
+This option does not affect extraction from archives.
 
 
 @opsummary{gzip}
 @opsummary{gzip}
 @opsummary{gunzip}
 @opsummary{gunzip}
@@ -3163,6 +3175,18 @@ file.  @var{user} can specify a symbolic name, or a numeric
 @acronym{ID}, or both as @var{name}:@var{id}.
 @acronym{ID}, or both as @var{name}:@var{id}.
 @xref{override}.
 @xref{override}.
 
 
+This option does not affect extraction from archives.  See also
+@option{--owner-map}, below.
+
+@opsummary{owner-map}
+@item --owner-map=@var{file}
+
+Read owner translation map from @var{file}.  This option allows to
+translate only certain owner names or UIDs.  @xref{override}, for a
+detailed description.  When used together with @option{--owner}
+option, the latter affects only those files whose owner is not listed
+in the @var{file}.
+
 This option does not affect extraction from archives.
 This option does not affect extraction from archives.
 
 
 @opsummary{pax-option}
 @opsummary{pax-option}
@@ -5259,6 +5283,70 @@ the argument @var{group} can be an existing group symbolic name, or a
 decimal numeric group @acronym{ID}, or @var{name}:@var{id}.
 decimal numeric group @acronym{ID}, or @var{name}:@var{id}.
 @end table
 @end table
 
 
+The @option{--owner} and @option{--group} options affect all files
+added to the archive.  @GNUTAR{} provides also two options that allow
+for more detailed control over owner translation:
+
+@table @option
+@item --owner-map=@var{file}
+Read UID translation map from @var{file}.
+
+When reading, empty lines are ignored.  The @samp{#} sign, unless
+quoted, introduces a comment, which extends to the end of the line.
+Each nonempty line defines mapping for a single UID.  It must consist
+of two fields separated by any amount of whitespace.  The first field
+defines original username and UID.  It can be a valid user name or
+a valid UID prefixed with a plus sign.  In both cases the
+corresponding UID or user name is inferred from the current host's
+user database.
+
+The second field defines the UID and username to map the original one
+to.  Its format can be the same as described above.  Otherwise, it can
+have the form @var{newname}:@var{newuid}, in which case neither
+@var{newname} nor @var{newuid} are required to be valid as per the
+user database.
+
+For example, consider the following file:
+
+@example
++10     bin
+smith   root:0
+@end example
+
+@noindent
+Given this file, each input file that is owner by UID 10 will be
+stored in archive with owner name @samp{bin} and owner UID
+corresponding to @samp{bin}.  Each file owned by user @samp{smith}
+will be stored with owner name @samp{root} and owner ID 0.  Other
+files will remain unchanged.
+
+When used together with @option{--owner-map}, the @option{--owner}
+option affects only files whose owner is not listed in the map file.
+
+@item --group-map=@var{file}
+Read GID translation map from @var{file}.
+
+The format of @var{file} is the same as for @option{--owner-map}
+option:
+
+Each nonempty line defines mapping for a single GID.  It must consist
+of two fields separated by any amount of whitespace.  The first field
+defines original group name and GID.  It can be a valid group name or
+a valid GID prefixed with a plus sign.  In both cases the
+corresponding GID or user name is inferred from the current host's
+group database.
+
+The second field defines the GID and group name to map the original one
+to.  Its format can be the same as described above.  Otherwise, it can
+have the form @var{newname}:@var{newgid}, in which case neither
+@var{newname} nor @var{newgid} are required to be valid as per the
+group database.
+
+When used together with @option{--group-map}, the @option{--group}
+option affects only files whose owner group is not rewritten using the
+map file.
+@end table
+
 @node Ignore Failed Read
 @node Ignore Failed Read
 @subsection Ignore Fail Read
 @subsection Ignore Fail Read
 
 

+ 1 - 0
src/Makefile.am

@@ -33,6 +33,7 @@ tar_SOURCES = \
  xheader.c\
  xheader.c\
  incremen.c\
  incremen.c\
  list.c\
  list.c\
+ map.c\
  misc.c\
  misc.c\
  names.c\
  names.c\
  sparse.c\
  sparse.c\

+ 7 - 0
src/common.h

@@ -952,4 +952,11 @@ void info_free_exclist (struct tar_stat_info *dir);
 bool excluded_name (char const *name, struct tar_stat_info *st);
 bool excluded_name (char const *name, struct tar_stat_info *st);
 void exclude_vcs_ignores (void);
 void exclude_vcs_ignores (void);
 
 
+/* Module map.c */
+void owner_map_read (char const *name);
+int owner_map_translate (uid_t uid, uid_t *new_uid, char const **new_name);
+void group_map_read (char const *file);
+int group_map_translate (gid_t gid, gid_t *new_gid, char const **new_name);
+
+
 _GL_INLINE_HEADER_END
 _GL_INLINE_HEADER_END

+ 9 - 9
src/create.c

@@ -741,17 +741,17 @@ union block *
 start_header (struct tar_stat_info *st)
 start_header (struct tar_stat_info *st)
 {
 {
   union block *header;
   union block *header;
-
+  char const *uname = NULL;
+  char const *gname = NULL;
+  
   header = write_header_name (st);
   header = write_header_name (st);
   if (!header)
   if (!header)
     return NULL;
     return NULL;
 
 
   /* Override some stat fields, if requested to do so.  */
   /* Override some stat fields, if requested to do so.  */
+  owner_map_translate (st->stat.st_uid, &st->stat.st_uid, &uname);
+  group_map_translate (st->stat.st_gid, &st->stat.st_gid, &gname);
 
 
-  if (owner_option != (uid_t) -1)
-    st->stat.st_uid = owner_option;
-  if (group_option != (gid_t) -1)
-    st->stat.st_gid = group_option;
   if (mode_option)
   if (mode_option)
     st->stat.st_mode =
     st->stat.st_mode =
       ((st->stat.st_mode & ~MODE_ALL)
       ((st->stat.st_mode & ~MODE_ALL)
@@ -910,13 +910,13 @@ start_header (struct tar_stat_info *st)
     }
     }
   else
   else
     {
     {
-      if (owner_name_option)
-	st->uname = xstrdup (owner_name_option);
+      if (uname)
+	st->uname = xstrdup (uname);
       else
       else
 	uid_to_uname (st->stat.st_uid, &st->uname);
 	uid_to_uname (st->stat.st_uid, &st->uname);
 
 
-      if (group_name_option)
-	st->gname = xstrdup (group_name_option);
+      if (gname)
+	st->gname = xstrdup (gname);
       else
       else
 	gid_to_gname (st->stat.st_gid, &st->gname);
 	gid_to_gname (st->stat.st_gid, &st->gname);
 
 

+ 283 - 0
src/map.c

@@ -0,0 +1,283 @@
+/* Owner/group mapping for tar
+
+   Copyright 2015 Free Software Foundation, Inc.
+
+   This file is part of GNU tar.
+
+   GNU tar 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 of the License, or
+   (at your option) any later version.
+
+   GNU tar 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/>.  */
+
+#include <system.h>
+#include "common.h"
+#include "wordsplit.h"
+#include <hash.h>
+#include <pwd.h>
+
+struct mapentry
+{
+  uintmax_t orig_id;
+  uintmax_t new_id;
+  char *new_name;
+};
+
+static size_t
+map_hash (void const *entry, size_t nbuckets)
+{
+  struct mapentry const *map = entry;
+  return map->orig_id % nbuckets;
+}
+
+static bool
+map_compare (void const *entry1, void const *entry2)
+{
+  struct mapentry const *map1 = entry1;
+  struct mapentry const *map2 = entry2;
+  return map1->orig_id == map2->orig_id;
+}
+
+static int
+parse_id (uintmax_t *retval,
+	  char const *arg, char const *what, uintmax_t maxval,
+	  char const *file, unsigned line)
+{
+  uintmax_t v;
+  char *p;
+  
+  errno = 0;
+  v = strtoumax (arg, &p, 10);
+  if (*p || errno)
+    {
+      error (0, 0, _("%s:%u: invalid %s: %s"),  file, line, what, arg);
+      return -1;
+    }
+  if (v > maxval)
+    {
+      error (0, 0, _("%s:%u: %s out of range: %s"), file, line, what, arg);
+      return -1;
+    }
+  *retval = v;
+  return 0;
+}
+
+static void
+map_read (Hash_table **ptab, char const *file,
+	  uintmax_t (*name_to_id) (char const *), char const *what,
+	  uintmax_t maxval)
+{
+  FILE *fp;
+  char *buf = NULL;
+  size_t bufsize = 0;
+  ssize_t n;
+  struct wordsplit ws;
+  int wsopt;
+  unsigned line;
+  int err = 0;
+  
+  fp = fopen (file, "r");
+  if (!fp)
+    open_fatal (file);
+
+  ws.ws_comment = "#";
+  wsopt = WRDSF_COMMENT | WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_SQUEEZE_DELIMS
+          | WRDSF_QUOTE;
+  line = 0;
+  while ((n = getline (&buf, &bufsize, fp)) > 0)
+    {
+      struct mapentry *ent;
+      uintmax_t orig_id, new_id;
+      char *name = NULL;
+      char *colon;
+      
+      ++line;
+      if (wordsplit (buf, &ws, wsopt))
+	FATAL_ERROR ((0, 0, _("%s:%u: cannot split line: %s"),
+		      file, line, wordsplit_strerror (&ws)));
+      wsopt |= WRDSF_REUSE;
+      if (ws.ws_wordc == 0)
+	continue;
+      if (ws.ws_wordc != 2)
+	{
+	  error (0, 0, _("%s:%u: malformed line"), file, line);
+	  err = 1;
+	  continue;
+	}
+
+      if (ws.ws_wordv[0][0] == '+')
+	{
+	  if (parse_id (&orig_id, ws.ws_wordv[0]+1, what, maxval, file, line)) 
+	    {
+	      err = 1;
+	      continue;
+	    }
+	}
+      else if (name_to_id)
+	{
+	  orig_id = name_to_id (ws.ws_wordv[0]);
+	  if (orig_id == UINTMAX_MAX)
+	    {
+	      error (0, 0, _("%s:%u: can't obtain %s of %s"),
+		     file, line, what, ws.ws_wordv[0]);
+	      err = 1;
+	      continue;
+	    }
+	}
+
+      colon = strchr (ws.ws_wordv[1], ':');
+      if (colon)
+	{
+	  if (colon > ws.ws_wordv[1])
+	    name = ws.ws_wordv[1];
+	  *colon++ = 0;
+	  if (parse_id (&new_id, colon, what, maxval, file, line)) 
+	    {
+	      err = 1;
+	      continue;
+	    }
+	}
+      else if (ws.ws_wordv[1][0] == '+')
+	{
+	  if (parse_id (&new_id, ws.ws_wordv[1], what, maxval, file, line)) 
+	    {
+	      err = 1;
+	      continue;
+	    }
+	}
+      else
+	{
+	  name = ws.ws_wordv[1];
+	  new_id = name_to_id (ws.ws_wordv[1]);
+	  if (new_id == UINTMAX_MAX)
+	    {
+	      error (0, 0, _("%s:%u: can't obtain %s of %s"),
+		     file, line, what, ws.ws_wordv[1]);
+	      err = 1;
+	      continue;
+	    }
+	}
+
+      ent = xmalloc (sizeof (*ent));
+      ent->orig_id = orig_id;
+      ent->new_id = new_id;
+      ent->new_name = name ? xstrdup (name) : NULL;
+      
+      if (!((*ptab
+	     || (*ptab = hash_initialize (0, 0, map_hash, map_compare, 0)))
+	    && hash_insert (*ptab, ent)))
+	xalloc_die ();
+    }
+  if (wsopt & WRDSF_REUSE)
+    wordsplit_free (&ws);
+  fclose (fp);
+  if (err)
+    FATAL_ERROR ((0, 0, _("errors reading map file")));
+}
+
+/* UID translation */
+
+static Hash_table *owner_map;
+
+static uintmax_t
+name_to_uid (char const *name)
+{
+  struct passwd *pw = getpwnam (name);
+  return pw ? pw->pw_uid : UINTMAX_MAX;
+}
+
+void
+owner_map_read (char const *file)
+{
+  map_read (&owner_map, file, name_to_uid, "UID", TYPE_MAXIMUM (uid_t));
+}
+
+int
+owner_map_translate (uid_t uid, uid_t *new_uid, char const **new_name)
+{
+  int rc = 1;
+  
+  if (owner_map)
+    {
+      struct mapentry ent, *res;
+  
+      ent.orig_id = uid;
+      res = hash_lookup (owner_map, &ent);
+      if (res)
+	{
+	  *new_uid = res->new_id;
+	  *new_name = res->new_name;
+	  return 0;
+	}
+    }
+
+  if (owner_option != (uid_t) -1)
+    {
+      *new_uid = owner_option;
+      rc = 0;
+    }
+  if (owner_name_option)
+    {
+      *new_name = owner_name_option;
+      rc = 0;
+    }
+
+  return rc;
+}
+
+/* GID translation */
+
+static Hash_table *group_map;
+
+static uintmax_t
+name_to_gid (char const *name)
+{
+  struct group *gr = getgrnam (name);
+  return gr ? gr->gr_gid : UINTMAX_MAX;
+}
+
+void
+group_map_read (char const *file)
+{
+  map_read (&group_map, file, name_to_gid, "GID", TYPE_MAXIMUM (gid_t));
+}
+
+int
+group_map_translate (gid_t gid, gid_t *new_gid, char const **new_name)
+{
+  int rc = 1;
+  
+  if (group_map)
+    {
+      struct mapentry ent, *res;
+  
+      ent.orig_id = gid;
+      res = hash_lookup (group_map, &ent);
+      if (res)
+	{
+	  *new_gid = res->new_id;
+	  *new_name = res->new_name;
+	  return 0;
+	}
+    }
+
+  if (group_option != (uid_t) -1)
+    {
+      *new_gid = group_option;
+      rc = 0;
+    }
+  if (group_name_option)
+    {
+      *new_name = group_name_option;
+      rc = 0;
+    }
+  
+  return rc;
+}

+ 14 - 0
src/tar.c

@@ -298,6 +298,7 @@ enum
   FORCE_LOCAL_OPTION,
   FORCE_LOCAL_OPTION,
   FULL_TIME_OPTION,
   FULL_TIME_OPTION,
   GROUP_OPTION,
   GROUP_OPTION,
+  GROUP_MAP_OPTION,
   IGNORE_CASE_OPTION,
   IGNORE_CASE_OPTION,
   IGNORE_COMMAND_ERROR_OPTION,
   IGNORE_COMMAND_ERROR_OPTION,
   IGNORE_FAILED_READ_OPTION,
   IGNORE_FAILED_READ_OPTION,
@@ -340,6 +341,7 @@ enum
   OVERWRITE_DIR_OPTION,
   OVERWRITE_DIR_OPTION,
   OVERWRITE_OPTION,
   OVERWRITE_OPTION,
   OWNER_OPTION,
   OWNER_OPTION,
+  OWNER_MAP_OPTION,
   PAX_OPTION,
   PAX_OPTION,
   POSIX_OPTION,
   POSIX_OPTION,
   PRESERVE_OPTION,
   PRESERVE_OPTION,
@@ -534,6 +536,10 @@ static struct argp_option options[] = {
    N_("force NAME as owner for added files"), GRID+1 },
    N_("force NAME as owner for added files"), GRID+1 },
   {"group", GROUP_OPTION, N_("NAME"), 0,
   {"group", GROUP_OPTION, N_("NAME"), 0,
    N_("force NAME as group for added files"), GRID+1 },
    N_("force NAME as group for added files"), GRID+1 },
+  {"owner-map", OWNER_MAP_OPTION, N_("FILE"), 0,
+   N_("use FILE to map file owner UIDs and names"), GRID+1 },
+  {"group-map", GROUP_MAP_OPTION, N_("FILE"), 0,
+   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 },
   {"mode", MODE_OPTION, N_("CHANGES"), 0,
   {"mode", MODE_OPTION, N_("CHANGES"), 0,
@@ -1996,6 +2002,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
       }
       }
       break;
       break;
 
 
+    case GROUP_MAP_OPTION:
+      group_map_read (arg);
+      break;
+      
     case MODE_OPTION:
     case MODE_OPTION:
       mode_option = mode_compile (arg);
       mode_option = mode_compile (arg);
       if (!mode_option)
       if (!mode_option)
@@ -2090,6 +2100,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
       }
       }
       break;
       break;
 
 
+    case OWNER_MAP_OPTION:
+      owner_map_read (arg);
+      break;
+      
     case QUOTE_CHARS_OPTION:
     case QUOTE_CHARS_OPTION:
       for (;*arg; arg++)
       for (;*arg; arg++)
 	set_char_quoting (NULL, *arg, 1);
 	set_char_quoting (NULL, *arg, 1);

+ 1 - 0
tests/Makefile.am

@@ -140,6 +140,7 @@ TESTSUITE_AT = \
  lustar01.at\
  lustar01.at\
  lustar02.at\
  lustar02.at\
  lustar03.at\
  lustar03.at\
+ map.at\
  multiv01.at\
  multiv01.at\
  multiv02.at\
  multiv02.at\
  multiv03.at\
  multiv03.at\

+ 71 - 0
tests/map.at

@@ -0,0 +1,71 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+#
+# Test suite for GNU tar.
+# Copyright 2015 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/>.
+
+# Check the --owner-map and --group-map options.
+
+AT_SETUP([--owner-map and --group-map])
+AT_KEYWORDS([owner map])
+
+AT_TAR_CHECK([
+export TZ=UTC0
+
+genfile --file a
+set -- `genfile --stat=uid,gid a`
+cat > uid.map <<EOT
+# Owner mapping
++$1	"Joe the Plumber:1234"
+EOT
+# Group mapping
+cat > gid.map <<EOT
++$2	"Plumber's Union:5678"
+EOT
+
+tar --owner-map=uid.map\
+    --group-map=gid.map\
+    --owner="Fallback Owner:4321" \
+    --group="Fallback Group:8765" \
+    --mtime='@0' \
+    --mode='u=rw,go=r' \
+    -cf 1.tar a
+
+tar -tvf 1.tar
+tar --numeric-owner -tvf 1.tar
+    
+> uid.map
+> gid.map
+
+tar --owner-map=uid.map\
+    --group-map=gid.map\
+    --owner="Fallback Owner:4321" \
+    --group="Fallback Group:8765" \
+    --mtime='@0' \
+    --mode='u=rw,go=r' \
+    -cf 2.tar a
+
+tar -tvf 2.tar
+tar --numeric-owner -tvf 2.tar
+],
+[0],
+[-rw-r--r-- Joe the Plumber/Plumber's Union 0 1970-01-01 00:00 a
+-rw-r--r-- 1234/5678         0 1970-01-01 00:00 a
+-rw-r--r-- Fallback Owner/Fallback Group 0 1970-01-01 00:00 a
+-rw-r--r-- 4321/8765         0 1970-01-01 00:00 a
+],
+[],[],[],[gnu])
+
+AT_CLEANUP

+ 1 - 0
tests/testsuite.at

@@ -349,6 +349,7 @@ m4_include([multiv08.at])
 
 
 AT_BANNER([Owner and Groups])
 AT_BANNER([Owner and Groups])
 m4_include([owner.at])
 m4_include([owner.at])
+m4_include([map.at])
 
 
 AT_BANNER([Sparse files])
 AT_BANNER([Sparse files])
 m4_include([sparse01.at])
 m4_include([sparse01.at])