diff --git a/configure.ac b/configure.ac index db69cb8..2afa463 100644 --- a/configure.ac +++ b/configure.ac @@ -223,6 +223,20 @@ AC_CHECK_TYPE(iconv_t,:, #endif ]) +AC_ARG_ENABLE(xattr, + AC_HELP_STRING([--enable-xattr], + [enable Extended Attribute support (disabled by default)]), + [xattr_enabled=$enableval], + [xattr_enabled=no]) + +if test "x$xattr_enabled" = xyes; then + AC_CHECK_HEADERS(attr/xattr.h) + AC_CHECK_FUNCS(getxattr fgetxattr lgetxattr \ + setxattr fsetxattr lsetxattr \ + listxattr flistxattr llistxattr, + AC_DEFINE(HAVE_XATTRS,1,[Define if we have a working extended attributes]),) +fi + # Gettext. AM_GNU_GETTEXT([external], [need-formatstring-macros]) AM_GNU_GETTEXT_VERSION([0.16]) diff --git a/doc/tar.texi b/doc/tar.texi index db8f986..d861d12 100644 --- a/doc/tar.texi +++ b/doc/tar.texi @@ -3002,6 +3002,10 @@ mechanism. Treat all input file or member names literally, do not interpret escape sequences. @xref{input name quoting}. +@opsummary{no-xattrs} +@item --no-xattrs +Causes @command{tar} not to store and not to extract xattrs. @xref{Attributes}. + @opsummary{no-wildcards} @item --no-wildcards Do not use wildcards. @@ -3447,6 +3451,10 @@ Enable or disable warning messages identified by @var{keyword}. The messages are suppressed if @var{keyword} is prefixed with @samp{no-}. @xref{warnings}. +@opsummary{xattrs} +@item --xattrs +Causes @command{tar} to store xattrs. @xref{Attributes}. + @opsummary{wildcards} @item --wildcards Use wildcards when matching member names with patterns. @@ -8659,6 +8667,8 @@ implementation able to read @samp{ustar} archives will be able to read most @samp{posix} archives as well, with the only exception that any additional information (such as long file names etc.) will in such case be extracted as plain text files along with the files it refers to. +This is the only format that can store ACLs, SELinux context and extended +attributes. This archive format will be the default format for future versions of @GNUTAR{}. @@ -9293,6 +9303,20 @@ Same as both @option{--same-permissions} and @option{--same-order}. This option is deprecated, and will be removed in @GNUTAR{} version 1.23. +@opindex xattrs +@item --xattrs +This option causes @command{tar} to store the current extended attributes in +the archive. + +The @option{--xattrs} option has no equivalent short option name. + +@opindex no-xattrs +@item --no-xattrs +This option causes @command{tar} not to store the current extended attributes in +the archive and not to extract any extended attributes in an archive. + +The @option{--no-xattrs} option has no equivalent short option name. + @end table @node Portability diff --git a/src/Makefile.am b/src/Makefile.am index de310f4..27c28be 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,7 +20,7 @@ bin_PROGRAMS = tar -noinst_HEADERS = arith.h common.h tar.h +noinst_HEADERS = arith.h common.h tar.h xattrs.h tar_SOURCES = \ buffer.c\ checkpoint.c\ @@ -42,10 +42,11 @@ tar_SOURCES = \ unlink.c\ update.c\ utf8.c\ - warning.c + warning.c\ + xattrs.c INCLUDES = -I$(top_srcdir)/gnu -I../ -I../gnu -I$(top_srcdir)/lib -I../lib LDADD = ../lib/libtar.a ../gnu/libgnu.a $(LIBINTL) $(LIBICONV) -tar_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_EACCESS) +tar_LDADD = $(LIBS) $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_EACCESS) diff --git a/src/common.h b/src/common.h index 0b9bd7a..e4ee345 100644 --- a/src/common.h +++ b/src/common.h @@ -253,6 +253,9 @@ GLOBAL int same_owner_option; /* If positive, preserve permissions when extracting. */ GLOBAL int same_permissions_option; +/* If positive, save the user and root xattrs. */ +GLOBAL int xattrs_option; + /* When set, strip the given number of file name components from the file name before extracting */ GLOBAL size_t strip_name_components; @@ -707,6 +710,9 @@ extern char *output_start; void update_archive (void); +/* Module attrs.c. */ +#include "xattrs.h" + /* Module xheader.c. */ void xheader_decode (struct tar_stat_info *stat); @@ -727,6 +733,12 @@ bool xheader_string_end (struct xheader *xhdr, char const *keyword); bool xheader_keyword_deleted_p (const char *kw); char *xheader_format_name (struct tar_stat_info *st, const char *fmt, size_t n); +void xheader_xattr_init(struct tar_stat_info *st); +void xheader_xattr_free(struct xattr_array *vals, size_t sz); +void xheader_xattr_copy(const struct tar_stat_info *st, + struct xattr_array **vals, size_t *sz); +void xheader_xattr_add(struct tar_stat_info *st, + const char *key, const char *val, size_t len); /* Module system.c */ diff --git a/src/create.c b/src/create.c index 43b5a4c..7ed5d10 100644 --- a/src/create.c +++ b/src/create.c @@ -936,6 +936,21 @@ start_header (struct tar_stat_info *st) GNAME_TO_CHARS (st->gname, header->header.gname); } + if (archive_format == POSIX_FORMAT) + { + if (xattrs_option > 0) + { + size_t scan_xattr = 0; + struct xattr_array *xattr_map = st->xattr_map; + + while (scan_xattr < st->xattr_map_size) + { + xheader_store (xattr_map[scan_xattr].xkey, st, &scan_xattr); + ++scan_xattr; + } + } + } + return header; } @@ -1711,6 +1726,11 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p) bool ok; struct stat final_stat; + if (fd == 0) + xattrs_xattrs_get(st, p, -1); + else + xattrs_xattrs_get(st, p, fd); + if (is_dir) { const char *tag_file_name; @@ -1829,6 +1849,8 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p) if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) < size) write_long_link (st); + xattrs_xattrs_get(st, p, -1); + block_ordinal = current_block_ordinal (); st->stat.st_size = 0; /* force 0 size on symlink */ header = start_header (st); @@ -1847,11 +1869,20 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p) } #endif else if (S_ISCHR (st->stat.st_mode)) - type = CHRTYPE; + { + type = CHRTYPE; + xattrs_xattrs_get(st, p, -1); + } else if (S_ISBLK (st->stat.st_mode)) - type = BLKTYPE; + { + type = BLKTYPE; + xattrs_xattrs_get(st, p, -1); + } else if (S_ISFIFO (st->stat.st_mode)) - type = FIFOTYPE; + { + type = FIFOTYPE; + xattrs_xattrs_get(st, p, -1); + } else if (S_ISSOCK (st->stat.st_mode)) { WARNOPT (WARN_FILE_IGNORED, diff --git a/src/extract.c b/src/extract.c index aaea56e..5c0a9c9 100644 --- a/src/extract.c +++ b/src/extract.c @@ -97,6 +97,9 @@ struct delayed_set_stat /* Directory that the name is relative to. */ int change_dir; + /* extended attributes*/ + size_t xattr_map_size; /* Size of the xattr map */ + struct xattr_array *xattr_map; /* Length and contents of name. */ size_t file_name_len; char file_name[1]; @@ -134,6 +137,9 @@ struct delayed_link hard-linked together. */ struct string_list *sources; + size_t xattr_map_size; /* Size of the xattr map */ + struct xattr_array *xattr_map; + /* The desired target of the desired link. */ char target[1]; }; @@ -335,6 +341,8 @@ set_stat (char const *file_name, utime_error (file_name); } + xattrs_xattrs_set(st, file_name, typeflag); + if (0 < same_owner_option && ! interdir) { /* Some systems allow non-root users to give files away. Once this @@ -431,6 +439,13 @@ delay_set_stat (char const *file_name, struct tar_stat_info const *st, data->atflag = atflag; data->after_links = 0; data->change_dir = chdir_current; + if (st) + xheader_xattr_copy (st, &data->xattr_map, &data->xattr_map_size); + else + { + data->xattr_map = NULL; + data->xattr_map_size = 0; + } strcpy (data->file_name, file_name); delayed_set_stat_head = data; if (must_be_dot_or_slash (file_name)) @@ -673,6 +688,31 @@ maybe_recoverable (char *file_name, bool regular, bool *interdir_made) return RECOVER_NO; } +/* Restore stat extended attributes (xattr) for FILE_NAME, using information + given in *ST. Restore before extraction because they may affect layout. + If not restoring permissions, invert the + INVERT_PERMISSIONS bits from the file's current permissions. + TYPEFLAG specifies the type of the file. + FILE_CREATED indicates set_xattr has created the file */ +static int +set_xattr (char const *file_name, struct tar_stat_info const *st, + mode_t invert_permissions, char typeflag, int *file_created) +{ + int status = 0; + bool interdir_made = false; + + if ((xattrs_option >= 0) && st->xattr_map_size) { + mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask; + + do + status = mknod (file_name, mode ^ invert_permissions, 0); + while (status && maybe_recoverable ((char *)file_name, false, &interdir_made)); + xattrs_xattrs_set(st, file_name, typeflag); + *file_created = 1; + } + return(status); +} + /* Fix the statuses of all directories whose statuses need fixing, and which are not ancestors of FILE_NAME. If AFTER_LINKS is nonzero, do this for all such directories; otherwise, stop at the @@ -733,12 +773,15 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links) sb.stat.st_gid = data->gid; sb.atime = data->atime; sb.mtime = data->mtime; + sb.xattr_map = data->xattr_map; + sb.xattr_map_size = data->xattr_map_size; set_stat (data->file_name, &sb, -1, current_mode, current_mode_mask, DIRTYPE, data->interdir, data->atflag); } delayed_set_stat_head = data->next; + xheader_xattr_free (data->xattr_map, data->xattr_map_size); free (data); } } @@ -854,6 +897,7 @@ extract_dir (char *file_name, int typeflag) static int open_output_file (char const *file_name, int typeflag, mode_t mode, + int file_created, mode_t *current_mode, mode_t *current_mode_mask) { int fd; @@ -864,6 +908,10 @@ open_output_file (char const *file_name, int typeflag, mode_t mode, ? O_TRUNC | (dereference_option ? 0 : O_NOFOLLOW) : O_EXCL)); + /* File might be created in set_xattr. So clear O_EXCL to avoid open() failure */ + if (file_created) + openflag = openflag & ~O_EXCL; + if (typeflag == CONTTYPE) { static int conttype_diagnosed; @@ -934,6 +982,7 @@ extract_file (char *file_name, int typeflag) bool interdir_made = false; mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0)); + mode_t invert_permissions = 0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0; mode_t current_mode = 0; mode_t current_mode_mask = 0; @@ -950,7 +999,17 @@ extract_file (char *file_name, int typeflag) } else { + int file_created = 0; + if (set_xattr (file_name, ¤t_stat_info, invert_permissions, + typeflag, &file_created)) + { + skip_member (); + open_error (file_name); + return 1; + } + while ((fd = open_output_file (file_name, typeflag, mode, + file_created, ¤t_mode, ¤t_mode_mask)) < 0) { @@ -1091,6 +1150,7 @@ create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made) + strlen (file_name) + 1); p->sources->next = 0; strcpy (p->sources->string, file_name); + xheader_xattr_copy (¤t_stat_info, &p->xattr_map, &p->xattr_map_size); strcpy (p->target, current_stat_info.link_name); h = delayed_set_stat_head; @@ -1525,6 +1585,8 @@ apply_delayed_links (void) st1.stat.st_gid = ds->gid; st1.atime = ds->atime; st1.mtime = ds->mtime; + st1.xattr_map = ds->xattr_map; + st1.xattr_map_size = ds->xattr_map_size; set_stat (source, &st1, -1, 0, 0, SYMTYPE, false, AT_SYMLINK_NOFOLLOW); valid_source = source; @@ -1539,6 +1601,8 @@ apply_delayed_links (void) sources = next; } + xheader_xattr_free (ds->xattr_map, ds->xattr_map_size); + { struct delayed_link *next = ds->next; free (ds); diff --git a/src/list.c b/src/list.c index cf2de09..6f52579 100644 --- a/src/list.c +++ b/src/list.c @@ -604,6 +604,8 @@ decode_header (union block *header, struct tar_stat_info *stat_info, assign_string (&stat_info->gname, header->header.gname[0] ? header->header.gname : NULL); + xheader_xattr_init(stat_info); + if (format == OLDGNU_FORMAT && incremental_option) { stat_info->atime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.atime); diff --git a/src/tar.c b/src/tar.c index 928cfdd..75510d8 100644 --- a/src/tar.c +++ b/src/tar.c @@ -304,6 +304,7 @@ enum NO_UNQUOTE_OPTION, NO_WILDCARDS_MATCH_SLASH_OPTION, NO_WILDCARDS_OPTION, + NO_XATTR_OPTION, NULL_OPTION, NUMERIC_OWNER_OPTION, OCCURRENCE_OPTION, @@ -340,7 +341,8 @@ enum VOLNO_FILE_OPTION, WARNING_OPTION, WILDCARDS_MATCH_SLASH_OPTION, - WILDCARDS_OPTION + WILDCARDS_OPTION, + XATTR_OPTION }; const char *argp_program_version = "tar (" PACKAGE_NAME ") " VERSION; @@ -516,6 +518,10 @@ static struct argp_option options[] = { {"preserve-order", 's', 0, 0, N_("sort names to extract to match archive"), GRID+1 }, {"same-order", 0, 0, OPTION_ALIAS, NULL, GRID+1 }, + {"xattrs", XATTR_OPTION, 0, 0, + N_("Save the user/root xattrs to the archive"), GRID+1 }, + {"no-xattrs", NO_XATTR_OPTION, 0, 0, + N_("Don't extract the user/root xattrs from the archive"), GRID+1 }, {"preserve", PRESERVE_OPTION, 0, 0, N_("same as both -p and -s"), GRID+1 }, {"delay-directory-restore", DELAY_DIRECTORY_RESTORE_OPTION, 0, 0, @@ -2079,6 +2085,15 @@ parse_opt (int key, char *arg, struct argp_state *state) same_permissions_option = -1; break; + case XATTR_OPTION: + set_archive_format ("posix"); + xattrs_option = 1; + break; + + case NO_XATTR_OPTION: + xattrs_option = -1; + break; + case RECURSION_OPTION: recursion_option = FNM_LEADING_DIR; break; @@ -2461,6 +2476,15 @@ decode_options (int argc, char **argv) || subcommand_option != LIST_SUBCOMMAND)) USAGE_ERROR ((0, 0, _("--pax-option can be used only on POSIX archives"))); + /* star create's non-POSIX typed archives with xattr support, so allow the + extra headers */ + if ((xattrs_option > 0) + && archive_format != POSIX_FORMAT + && (subcommand_option != EXTRACT_SUBCOMMAND + || subcommand_option != DIFF_SUBCOMMAND + || subcommand_option != LIST_SUBCOMMAND)) + USAGE_ERROR ((0, 0, _("--xattrs can be used only on POSIX archives"))); + /* If ready to unlink hierarchies, so we are for simpler files. */ if (recursive_unlink_option) old_files_option = UNLINK_FIRST_OLD_FILES; @@ -2713,6 +2737,7 @@ void tar_stat_destroy (struct tar_stat_info *st) { tar_stat_close (st); + xheader_xattr_free (st->xattr_map, st->xattr_map_size); free (st->orig_file_name); free (st->file_name); free (st->link_name); diff --git a/src/tar.h b/src/tar.h index ce9850c..955b18e 100644 --- a/src/tar.h +++ b/src/tar.h @@ -276,6 +276,14 @@ struct xheader uintmax_t string_length; }; +/* Information about xattrs for a file. */ +struct xattr_array + { + char *xkey; + char *xval_ptr; + size_t xval_len; + }; + struct tar_stat_info { char *orig_file_name; /* name of file read from the archive header */ @@ -287,6 +295,7 @@ struct tar_stat_info char *uname; /* user name of owner */ char *gname; /* group name of owner */ + struct stat stat; /* regular filesystem stat */ /* STAT doesn't always have access, data modification, and status @@ -309,6 +318,9 @@ struct tar_stat_info size_t sparse_map_size; /* Size of the sparse map */ struct sp_array *sparse_map; + size_t xattr_map_size; /* Size of the xattr map */ + struct xattr_array *xattr_map; + /* Extended headers */ struct xheader xhdr; diff --git a/src/xattrs.c b/src/xattrs.c new file mode 100644 index 0000000..6a9950e --- /dev/null +++ b/src/xattrs.c @@ -0,0 +1,181 @@ +/* Create a tar archive. + + Copyright (C) 2006 Free Software Foundation, Inc. + + Written by James Antill, on 2006-07-27. + + 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 2, 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include + +#include + +#include "common.h" + + +#ifndef HAVE_ATTR_XATTR_H +# undef HAVE_XATTRS +#endif + +#ifdef HAVE_ATTR_XATTR_H +# include +#endif + + +void xattrs_xattrs_get(struct tar_stat_info *st, char const *file_name, int fd) +{ + if (xattrs_option > 0) + { /* get all xattrs ... this include security.* and system.* if + available. We filter them here, but we have to filter them + in xattrs_xattrs_set() anyway. + */ + static ssize_t xsz = 1024; + static char *xatrs = NULL; + ssize_t xret = -1; + +#ifndef HAVE_XATTRS + static int done = 0; + if ((xattrs_option > 0) && !done) + WARN ((0, 0, _("Xattr support requested, but not available"))); + done = 1; +#else + + if (!xatrs) xatrs = xmalloc (xsz); + + while (((fd == -1) ? + ((xret = llistxattr (file_name, xatrs, xsz)) == -1) : + ((xret = flistxattr (fd, xatrs, xsz)) == -1)) && + (errno == ERANGE)) + { + xsz <<= 1; + xatrs = xrealloc (xatrs, xsz); + } + + if (xret == -1) + call_arg_warn ((fd == -1) ? "llistxattrs" : "flistxattrs", file_name); + else + { + const char *attr = xatrs; + static ssize_t asz = 1024; + static char *val = NULL; + + if (!val) val = xmalloc (asz); + + while (xret > 0) + { + size_t len = strlen (attr); + ssize_t aret = 0; + + /* Archive all xattrs during creation, decide at extraction time + * which ones are of interest/use for the target filesystem. */ + while (((fd == -1) ? + ((aret = lgetxattr (file_name, attr, val, asz)) == -1) : + ((aret = fgetxattr (fd, attr, val, asz)) == -1)) && + (errno == ERANGE)) + { + asz <<= 1; + val = xrealloc (val, asz); + } + + if (aret != -1) + xheader_xattr_add (st, attr, val, aret); + else if (errno != ENOATTR) + call_arg_warn ((fd==-1) ? "lgetxattr" : "fgetxattr", file_name); + + attr += len + 1; + xret -= len + 1; + } + } +#endif + } +} + +static void xattrs__fd_set(struct tar_stat_info const *st, + char const *file_name, char typeflag, + const char *attr, + const char *ptr, size_t len) +{ +#ifdef HAVE_XATTRS + if (ptr) + { + const char *sysname = "setxattr"; + int ret = -1; + + if (typeflag != SYMTYPE) + ret = setxattr (file_name, attr, ptr, len, 0); + else + { + sysname = "lsetxattr"; + ret = lsetxattr (file_name, attr, ptr, len, 0); + } + + /* do not print warnings when SELinux is disabled */ + if ((ret == -1) && (errno != EPERM) && (errno != ENOTSUP)) + call_arg_error(sysname, file_name); + } +#endif +} + +static char *skip_to_ext_fields(char *ptr) +{ + ptr += strcspn(ptr, ":,\n"); /* skip tag name. Ie. user/group/default/mask */ + + if (*ptr != ':') + return (ptr); /* error? no user/group field */ + ++ptr; + + ptr += strcspn(ptr, ":,\n"); /* skip user/group name */ + + if (*ptr != ':') + return (ptr); /* error? no perms field */ + ++ptr; + + ptr += strcspn(ptr, ":,\n"); /* skip perms */ + + if (*ptr != ':') + return (ptr); /* no extra fields */ + + return (ptr); +} + +void xattrs_xattrs_set(struct tar_stat_info const *st, + char const *file_name, char typeflag) +{ + if ((xattrs_option >= 0) && st->xattr_map_size) + { + size_t scan = 0; + +#ifndef HAVE_XATTRS + static int done = 0; + if (!done) + WARN ((0, 0, _("Xattr support requested, but not available"))); + done = 1; +#else + while (scan < st->xattr_map_size) + { + char *keyword = st->xattr_map[scan].xkey; + + /* assert (!memcpy (keyword, "SCHILY.xattr.", strlen("SCHILY.xattr."))); */ + keyword += strlen("SCHILY.xattr."); + + xattrs__fd_set (st, file_name, typeflag, keyword, + st->xattr_map[scan].xval_ptr, + st->xattr_map[scan].xval_len); + + ++scan; + } +#endif + } +} diff --git a/src/xattrs.h b/src/xattrs.h new file mode 100644 index 0000000..7ffdce1 --- /dev/null +++ b/src/xattrs.h @@ -0,0 +1,6 @@ + +extern void xattrs_xattrs_get(struct tar_stat_info *st, + char const *file_name, int fd); + +extern void xattrs_xattrs_set(struct tar_stat_info const *st, + char const *file_name, char typeflag); diff --git a/src/xheader.c b/src/xheader.c index 2284e97..557b3e5 100644 --- a/src/xheader.c +++ b/src/xheader.c @@ -460,6 +460,74 @@ xheader_write_global (struct xheader *xhdr) } } +void xheader_xattr_init(struct tar_stat_info *st) +{ + st->xattr_map = NULL; + st->xattr_map_size = 0; +} + +void xheader_xattr_free(struct xattr_array *xattr_map, size_t xattr_map_size) +{ + size_t scan = 0; + + while (scan < xattr_map_size) + { + free (xattr_map[scan].xkey); + free (xattr_map[scan].xval_ptr); + + ++scan; + } + free (xattr_map); +} + +static void xheader_xattr__add(struct xattr_array **xattr_map, + size_t *xattr_map_size, + const char *key, const char *val, size_t len) +{ + size_t pos = (*xattr_map_size)++; + + *xattr_map = xrealloc (*xattr_map, + *xattr_map_size * sizeof(struct xattr_array)); + (*xattr_map)[pos].xkey = xstrdup (key); + (*xattr_map)[pos].xval_ptr = xmemdup (val, len + 1); + (*xattr_map)[pos].xval_len = len; +} + +void xheader_xattr_add(struct tar_stat_info *st, + const char *key, const char *val, size_t len) +{ + size_t klen = strlen (key); + char *xkey = xmalloc (strlen("SCHILY.xattr.") + klen + 1); + char *tmp = xkey; + + tmp = stpcpy (tmp, "SCHILY.xattr."); + tmp = stpcpy (tmp, key); + + xheader_xattr__add (&st->xattr_map, &st->xattr_map_size, xkey, val, len); + + free (xkey); +} + +void xheader_xattr_copy(const struct tar_stat_info *st, + struct xattr_array **xattr_map, size_t *xattr_map_size) +{ + size_t scan = 0; + + *xattr_map = NULL; + *xattr_map_size = 0; + + while (scan < st->xattr_map_size) + { + char *key = st->xattr_map[scan].xkey; + char *val = st->xattr_map[scan].xval_ptr; + size_t len = st->xattr_map[scan].xval_len; + + xheader_xattr__add(xattr_map, xattr_map_size, key, val, len); + + ++scan; + } +} + /* General Interface */ @@ -473,6 +541,7 @@ struct xhdr_tab struct xheader *, void const *data); void (*decoder) (struct tar_stat_info *, char const *, char const *, size_t); int flags; + bool prefix; }; /* This declaration must be extern, because ISO C99 section 6.9.2 @@ -489,8 +558,17 @@ locate_handler (char const *keyword) struct xhdr_tab const *p; for (p = xhdr_tab; p->keyword; p++) - if (strcmp (p->keyword, keyword) == 0) - return p; + if (p->prefix) + { + if (strncmp (p->keyword, keyword, strlen(p->keyword)) == 0) + return p; + } + else + { + if (strcmp (p->keyword, keyword) == 0) + return p; + } + return NULL; } @@ -500,7 +578,7 @@ xheader_protected_pattern_p (const char *pattern) struct xhdr_tab const *p; for (p = xhdr_tab; p->keyword; p++) - if ((p->flags & XHDR_PROTECTED) && fnmatch (pattern, p->keyword, 0) == 0) + if (!p->prefix && (p->flags & XHDR_PROTECTED) && fnmatch (pattern, p->keyword, 0) == 0) return true; return false; } @@ -511,7 +589,7 @@ xheader_protected_keyword_p (const char *keyword) struct xhdr_tab const *p; for (p = xhdr_tab; p->keyword; p++) - if ((p->flags & XHDR_PROTECTED) && strcmp (p->keyword, keyword) == 0) + if (!p->prefix && (p->flags & XHDR_PROTECTED) && strcmp (p->keyword, keyword) == 0) return true; return false; } @@ -1470,6 +1548,27 @@ volume_filename_decoder (struct tar_stat_info *st, } static void +xattr_coder (struct tar_stat_info const *st , char const *keyword, + struct xheader *xhdr, void const *data) +{ + struct xattr_array *xattr_map = st->xattr_map; + const size_t *off = data; + xheader_print_n (xhdr, keyword, + xattr_map[*off].xval_ptr, xattr_map[*off].xval_len); +} + +static void +xattr_decoder (struct tar_stat_info *st, + char const *keyword, char const *arg, size_t size) +{ + char *xstr = NULL; + + xstr = xmemdup(arg, size + 1); + xheader_xattr_add(st, keyword + strlen("SCHILY.xattr."), xstr, size); + free(xstr); +} + +static void sparse_major_coder (struct tar_stat_info const *st, char const *keyword, struct xheader *xhdr, void const *data) { @@ -1506,53 +1605,53 @@ sparse_minor_decoder (struct tar_stat_info *st, } struct xhdr_tab const xhdr_tab[] = { - { "atime", atime_coder, atime_decoder, 0 }, - { "comment", dummy_coder, dummy_decoder, 0 }, - { "charset", dummy_coder, dummy_decoder, 0 }, - { "ctime", ctime_coder, ctime_decoder, 0 }, - { "gid", gid_coder, gid_decoder, 0 }, - { "gname", gname_coder, gname_decoder, 0 }, - { "linkpath", linkpath_coder, linkpath_decoder, 0 }, - { "mtime", mtime_coder, mtime_decoder, 0 }, - { "path", path_coder, path_decoder, 0 }, - { "size", size_coder, size_decoder, 0 }, - { "uid", uid_coder, uid_decoder, 0 }, - { "uname", uname_coder, uname_decoder, 0 }, + { "atime", atime_coder, atime_decoder, 0, false }, + { "comment", dummy_coder, dummy_decoder, 0, false }, + { "charset", dummy_coder, dummy_decoder, 0, false }, + { "ctime", ctime_coder, ctime_decoder, 0, false }, + { "gid", gid_coder, gid_decoder, 0, false }, + { "gname", gname_coder, gname_decoder, 0, false }, + { "linkpath", linkpath_coder, linkpath_decoder, 0, false }, + { "mtime", mtime_coder, mtime_decoder, 0, false }, + { "path", path_coder, path_decoder, 0, false }, + { "size", size_coder, size_decoder, 0, false }, + { "uid", uid_coder, uid_decoder, 0, false }, + { "uname", uname_coder, uname_decoder, 0, false }, /* Sparse file handling */ { "GNU.sparse.name", path_coder, path_decoder, - XHDR_PROTECTED }, + XHDR_PROTECTED, false }, { "GNU.sparse.major", sparse_major_coder, sparse_major_decoder, - XHDR_PROTECTED }, + XHDR_PROTECTED, false }, { "GNU.sparse.minor", sparse_minor_coder, sparse_minor_decoder, - XHDR_PROTECTED }, + XHDR_PROTECTED, false }, { "GNU.sparse.realsize", sparse_size_coder, sparse_size_decoder, - XHDR_PROTECTED }, + XHDR_PROTECTED, false }, { "GNU.sparse.numblocks", sparse_numblocks_coder, sparse_numblocks_decoder, - XHDR_PROTECTED }, + XHDR_PROTECTED, false }, /* tar 1.14 - 1.15.90 keywords. */ { "GNU.sparse.size", sparse_size_coder, sparse_size_decoder, - XHDR_PROTECTED }, + XHDR_PROTECTED, false }, /* tar 1.14 - 1.15.1 keywords. Multiple instances of these appeared in 'x' headers, and each of them was meaningful. It confilcted with POSIX specs, which requires that "when extended header records conflict, the last one given in the header shall take precedence." */ { "GNU.sparse.offset", sparse_offset_coder, sparse_offset_decoder, - XHDR_PROTECTED }, + XHDR_PROTECTED, false }, { "GNU.sparse.numbytes", sparse_numbytes_coder, sparse_numbytes_decoder, - XHDR_PROTECTED }, + XHDR_PROTECTED, false }, /* tar 1.15.90 keyword, introduced to remove the above-mentioned conflict. */ { "GNU.sparse.map", NULL /* Unused, see pax_dump_header() */, - sparse_map_decoder, 0 }, + sparse_map_decoder, 0, false }, { "GNU.dumpdir", dumpdir_coder, dumpdir_decoder, - XHDR_PROTECTED }, + XHDR_PROTECTED, false }, /* Keeps the tape/volume label. May be present only in the global headers. Equivalent to GNUTYPE_VOLHDR. */ { "GNU.volume.label", volume_label_coder, volume_label_decoder, - XHDR_PROTECTED | XHDR_GLOBAL }, + XHDR_PROTECTED | XHDR_GLOBAL, false }, /* These may be present in a first global header of the archive. They provide the same functionality as GNUTYPE_MULTIVOL header. @@ -1561,11 +1660,14 @@ struct xhdr_tab const xhdr_tab[] = { GNU.volume.offset keeps the offset of the start of this volume, otherwise kept in oldgnu_header.offset. */ { "GNU.volume.filename", volume_label_coder, volume_filename_decoder, - XHDR_PROTECTED | XHDR_GLOBAL }, + XHDR_PROTECTED | XHDR_GLOBAL, false }, { "GNU.volume.size", volume_size_coder, volume_size_decoder, - XHDR_PROTECTED | XHDR_GLOBAL }, + XHDR_PROTECTED | XHDR_GLOBAL, false }, { "GNU.volume.offset", volume_offset_coder, volume_offset_decoder, - XHDR_PROTECTED | XHDR_GLOBAL }, + XHDR_PROTECTED | XHDR_GLOBAL, false }, + + /* xattrs use the star format. note we only save some variants... */ + { "SCHILY.xattr", xattr_coder, xattr_decoder, 0, true }, - { NULL, NULL, NULL, 0 } + { NULL, NULL, NULL, 0, false } };