From eb36031906b623c41398f29df9178b5214a4c9fa Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Mon, 26 May 2008 17:10:10 +0000 Subject: [PATCH] MFp4: bsdtar 2.5.4b In addition to a number of bug fixes and minor changes: * --numeric-owner (ignore user/group names on create and extract) * -S (sparsify files on extraction) * -s (regex filename substitutions) * Use new libarchive 'linkify' to get correct hardlink handling for both old and new cpio formats * Rework 'copy' test to be insensitive to readdir() filename ordering Most of the credit for this work goes to Joerg Sonnenberger, who has been duplicating features from NetBSD's 'pax' program. --- usr.bin/tar/Makefile | 7 +- usr.bin/tar/bsdtar.1 | 40 +++- usr.bin/tar/bsdtar.c | 22 ++- usr.bin/tar/bsdtar.h | 10 +- usr.bin/tar/config_freebsd.h | 1 + usr.bin/tar/matching.c | 23 +++ usr.bin/tar/read.c | 7 + usr.bin/tar/subst.c | 275 +++++++++++++++++++++++++++ usr.bin/tar/test/Makefile | 1 + usr.bin/tar/test/test_copy.c | 190 +++++++++++-------- usr.bin/tar/test/test_option_T.c | 19 ++ usr.bin/tar/test/test_patterns.c | 47 +++++ usr.bin/tar/util.c | 60 +++++- usr.bin/tar/write.c | 309 +++++++++---------------------- 14 files changed, 695 insertions(+), 316 deletions(-) create mode 100644 usr.bin/tar/subst.c create mode 100644 usr.bin/tar/test/test_patterns.c diff --git a/usr.bin/tar/Makefile b/usr.bin/tar/Makefile index b4fe8b53257..5e898eb9a4c 100644 --- a/usr.bin/tar/Makefile +++ b/usr.bin/tar/Makefile @@ -1,8 +1,8 @@ # $FreeBSD$ PROG= bsdtar -BSDTAR_VERSION_STRING=2.5.0b -SRCS= bsdtar.c getdate.y matching.c read.c siginfo.c tree.c util.c write.c +BSDTAR_VERSION_STRING=2.5.4b +SRCS= bsdtar.c getdate.y matching.c read.c siginfo.c subst.c tree.c util.c write.c WARNS?= 5 DPADD= ${LIBARCHIVE} ${LIBBZ2} ${LIBZ} LDADD= -larchive -lbz2 -lz @@ -12,7 +12,8 @@ CFLAGS+= -I${.CURDIR} SYMLINKS= bsdtar ${BINDIR}/tar MLINKS= bsdtar.1 tar.1 -check: $(PROG) +.PHONY: check test +check test: $(PROG) bsdtar.1.gz cd ${.CURDIR}/test && make clean test .include diff --git a/usr.bin/tar/bsdtar.1 b/usr.bin/tar/bsdtar.1 index db437ed96db..0dc4dee368a 100644 --- a/usr.bin/tar/bsdtar.1 +++ b/usr.bin/tar/bsdtar.1 @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd April 13, 2004 +.Dd May 15, 2008 .Dt BSDTAR 1 .Os .Sh NAME @@ -278,6 +278,10 @@ This is often used to read filenames output by the .Fl print0 option to .Xr find 1 . +.It Fl -numeric-owner +(x mode only) +Ignore symbolic user and group names when restoring archives to disk, +only numeric uid and gid values will be obeyed. .It Fl O (x, t modes only) In extract (-x) mode, files will be written to standard out rather than @@ -294,7 +298,7 @@ is specified, and the program is being run by the root user. In this case, the file modes and flags from the archive will be restored, but ACLs or owner information in the archive will be discarded. -.Pp +.It Fl o (c, r, u mode) A synonym for .Fl -format Ar ustar @@ -335,12 +339,34 @@ By default, the archive is always read to the very end, since there can be multiple entries with the same name and, by convention, later entries overwrite earlier entries. This option is provided as a performance optimization. +.It Fl S +(x mode only) +Extract files as sparse files. +For every block on disk, check first if it contains only NULL bytes and seek +over it otherwise. +This works similiar to the conv=sparse option of dd. .It Fl -strip-components Ar count ( Fl W Cm strip-components Ns = Ns Ar count ) (x and t mode only) Remove the specified number of leading path elements. Pathnames with fewer elements will be silently skipped. Note that the pathname is edited after checking inclusion/exclusion patterns but before security checks. +.It Fl s Ar pattern +Modify file or archive member names according to +.Pa pattern . +The pattern has the format /old/new/[gps]. +old is a basic regular expression. +If it doesn't apply, the pattern is skipped. +new is the replacement string of the matched part. +~ is substituted with the match, \1 to \9 with the content of +the corresponding captured group. +The optional trailing g specifies that matching should continue +after the matched part and stopped on the first unmatched pattern. +The optional trailing s specifies that the pattern applies to the value +of symbolic links. +The optional trailing p specifies that after a successful substitution +the original path name and the new path name should be printed to +standard error. .It Fl T Ar filename In x or t mode, .Nm @@ -512,6 +538,16 @@ directory and add .Pa foo2 to the output archive. .Pp +An input file in +.Xr mtree 5 +format can be used to create an output archive with arbitrary ownership, +permissions, or names that differ from existing data on disk: +.Pp +.Dl $ cat input.mtree +.Dl usr/bin uid=0 gid=0 mode=0755 type=dir +.Dl usr/bin/ls uid=0 gid=0 mode=0755 type=file content=myls +.Dl $ tar -cvf output.tar @input.mtree +.Pp The .Fl -newer and diff --git a/usr.bin/tar/bsdtar.c b/usr.bin/tar/bsdtar.c index 674c88d7579..cc571388b04 100644 --- a/usr.bin/tar/bsdtar.c +++ b/usr.bin/tar/bsdtar.c @@ -118,7 +118,7 @@ static void version(void); * non-option. Otherwise, GNU getopt() permutes the arguments and * screws up -C processing. */ -static const char *tar_opts = "+Bb:C:cf:HhI:jkLlmnOoPprtT:UuvW:wX:xyZz"; +static const char *tar_opts = "+Bb:C:cf:HhI:jkLlmnOoPprts:ST:UuvW:wX:xyZz"; /* * Most of these long options are deliberately not documented. They @@ -151,6 +151,7 @@ enum { OPTION_NO_SAME_OWNER, OPTION_NO_SAME_PERMISSIONS, OPTION_NULL, + OPTION_NUMERIC_OWNER, OPTION_ONE_FILE_SYSTEM, OPTION_POSIX, OPTION_STRIP_COMPONENTS, @@ -207,6 +208,7 @@ static const struct option tar_longopts[] = { { "no-same-owner", no_argument, NULL, OPTION_NO_SAME_OWNER }, { "no-same-permissions",no_argument, NULL, OPTION_NO_SAME_PERMISSIONS }, { "null", no_argument, NULL, OPTION_NULL }, + { "numeric-owner", no_argument, NULL, OPTION_NUMERIC_OWNER }, { "one-file-system", no_argument, NULL, OPTION_ONE_FILE_SYSTEM }, { "posix", no_argument, NULL, OPTION_POSIX }, { "preserve-permissions", no_argument, NULL, 'p' }, @@ -460,6 +462,9 @@ main(int argc, char **argv) case OPTION_NULL: /* GNU tar */ bsdtar->option_null++; break; + case OPTION_NUMERIC_OWNER: /* GNU tar */ + bsdtar->option_numeric_owner++; + break; case 'O': /* GNU tar */ bsdtar->option_stdout = 1; break; @@ -499,6 +504,17 @@ main(int argc, char **argv) case 'r': /* SUSv2 */ set_mode(bsdtar, opt); break; + case 'S': /* NetBSD pax-as-tar */ + bsdtar->extract_flags |= ARCHIVE_EXTRACT_SPARSE; + break; + case 's': /* NetBSD pax-as-tar */ +#if HAVE_REGEX_H + add_substitution(bsdtar, optarg); +#else + bsdtar_warnc(bsdtar, 0, "-s is not supported by this version of bsdtar"); + usage(bsdtar); +#endif + break; case OPTION_STRIP_COMPONENTS: /* GNU tar 1.15 */ bsdtar->strip_components = atoi(optarg); break; @@ -674,6 +690,10 @@ main(int argc, char **argv) } cleanup_exclusions(bsdtar); +#if HAVE_REGEX_H + cleanup_substitution(bsdtar); +#endif + if (bsdtar->return_value != 0) bsdtar_warnc(bsdtar, 0, "Error exit delayed from previous errors."); diff --git a/usr.bin/tar/bsdtar.h b/usr.bin/tar/bsdtar.h index ee2550883df..e846ddd2f6c 100644 --- a/usr.bin/tar/bsdtar.h +++ b/usr.bin/tar/bsdtar.h @@ -65,6 +65,7 @@ struct bsdtar { char option_no_owner; /* -o */ char option_no_subdirs; /* -n */ char option_null; /* --null */ + char option_numeric_owner; /* --numeric-owner */ char option_stdout; /* -O */ char option_totals; /* --totals */ char option_unlink_first; /* -U */ @@ -90,13 +91,14 @@ struct bsdtar { * Data for various subsystems. Full definitions are located in * the file where they are used. */ + struct archive_entry_linkresolver *resolver; struct archive_dir *archive_dir; /* for write.c */ struct name_cache *gname_cache; /* for write.c */ - struct links_cache *links_cache; /* for write.c */ struct matching *matching; /* for matching.c */ struct security *security; /* for read.c */ struct name_cache *uname_cache; /* for write.c */ struct siginfo_data *siginfo; /* for siginfo.c */ + struct substitution *substitution; /* for subst.c */ }; void bsdtar_errc(struct bsdtar *, int _eval, int _code, @@ -126,6 +128,12 @@ void tar_mode_t(struct bsdtar *bsdtar); void tar_mode_u(struct bsdtar *bsdtar); void tar_mode_x(struct bsdtar *bsdtar); int unmatched_inclusions(struct bsdtar *bsdtar); +int unmatched_inclusions_warn(struct bsdtar *bsdtar, const char *msg); void usage(struct bsdtar *); int yes(const char *fmt, ...); +#if HAVE_REGEX_H +void add_substitution(struct bsdtar *, const char *); +int apply_substitution(struct bsdtar *, const char *, char **, int); +void cleanup_substitution(struct bsdtar *); +#endif diff --git a/usr.bin/tar/config_freebsd.h b/usr.bin/tar/config_freebsd.h index e118ef1af4b..c25430cc0da 100644 --- a/usr.bin/tar/config_freebsd.h +++ b/usr.bin/tar/config_freebsd.h @@ -77,6 +77,7 @@ #endif #define HAVE_PATHS_H 1 #define HAVE_PWD_H 1 +#define HAVE_REGEX_H 1 #define HAVE_SETLOCALE 1 #define HAVE_STDARG_H 1 #define HAVE_STDINT_H 1 diff --git a/usr.bin/tar/matching.c b/usr.bin/tar/matching.c index d0e60f254ae..782f7610416 100644 --- a/usr.bin/tar/matching.c +++ b/usr.bin/tar/matching.c @@ -259,6 +259,29 @@ unmatched_inclusions(struct bsdtar *bsdtar) } +int +unmatched_inclusions_warn(struct bsdtar *bsdtar, const char *msg) +{ + struct matching *matching; + struct match *p; + + matching = bsdtar->matching; + if (matching == NULL) + return (0); + + p = matching->inclusions; + while (p != NULL) { + if (p->matches == 0) { + bsdtar->return_value = 1; + bsdtar_warnc(bsdtar, 0, "%s: %s", + p->pattern, msg); + } + p = p->next; + } + return (matching->inclusions_unmatched_count); +} + + #if defined(HAVE_FNMATCH) && defined(HAVE_FNM_LEADING_DIR) diff --git a/usr.bin/tar/read.c b/usr.bin/tar/read.c index 5c14a4e30f0..87069062a94 100644 --- a/usr.bin/tar/read.c +++ b/usr.bin/tar/read.c @@ -77,6 +77,7 @@ void tar_mode_t(struct bsdtar *bsdtar) { read_archive(bsdtar, 't'); + unmatched_inclusions_warn(bsdtar, "Not found in archive"); } void @@ -87,6 +88,7 @@ tar_mode_x(struct bsdtar *bsdtar) read_archive(bsdtar, 'x'); + unmatched_inclusions_warn(bsdtar, "Not found in archive"); /* Restore old SIGINFO + SIGUSR1 handlers. */ siginfo_done(bsdtar); } @@ -170,6 +172,11 @@ read_archive(struct bsdtar *bsdtar, char mode) if (r == ARCHIVE_FATAL) break; + if (bsdtar->option_numeric_owner) { + archive_entry_set_uname(entry, NULL); + archive_entry_set_gname(entry, NULL); + } + /* * Exclude entries that are too old. */ diff --git a/usr.bin/tar/subst.c b/usr.bin/tar/subst.c new file mode 100644 index 00000000000..9fcd4d95fcf --- /dev/null +++ b/usr.bin/tar/subst.c @@ -0,0 +1,275 @@ +/*- + * Copyright (c) 2008 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "bsdtar_platform.h" +__FBSDID("$FreeBSD$"); + +#if HAVE_REGEX_H +#include "bsdtar.h" + +#include +#include +#include +#include + +struct subst_rule { + struct subst_rule *next; + regex_t re; + char *result; + int global:1, print:1, symlink:1; +}; + +struct substitution { + struct subst_rule *first_rule, *last_rule; +}; + +static void +init_substitution(struct bsdtar *bsdtar) +{ + struct substitution *subst; + + bsdtar->substitution = subst = malloc(sizeof(*subst)); + if (subst == NULL) + bsdtar_errc(bsdtar, 1, errno, "Out of memory"); + subst->first_rule = subst->last_rule = NULL; +} + +void +add_substitution(struct bsdtar *bsdtar, const char *rule_text) +{ + struct subst_rule *rule; + struct substitution *subst; + const char *end_pattern, *start_subst; + char *pattern; + int r; + + if ((subst = bsdtar->substitution) == NULL) { + init_substitution(bsdtar); + subst = bsdtar->substitution; + } + + rule = malloc(sizeof(*rule)); + if (rule == NULL) + bsdtar_errc(bsdtar, 1, errno, "Out of memory"); + rule->next = NULL; + + if (subst->last_rule == NULL) + subst->first_rule = rule; + else + subst->last_rule->next = rule; + subst->last_rule = rule; + + if (*rule_text == '\0') + bsdtar_errc(bsdtar, 1, 0, "Empty replacement string"); + end_pattern = strchr(rule_text + 1, *rule_text); + if (end_pattern == NULL) + bsdtar_errc(bsdtar, 1, 0, "Invalid replacement string"); + + pattern = malloc(end_pattern - rule_text); + if (pattern == NULL) + bsdtar_errc(bsdtar, 1, errno, "Out of memory"); + memcpy(pattern, rule_text + 1, end_pattern - rule_text - 1); + pattern[end_pattern - rule_text - 1] = '\0'; + + if ((r = regcomp(&rule->re, pattern, REG_BASIC)) != 0) { + char buf[80]; + regerror(r, &rule->re, buf, sizeof(buf)); + bsdtar_errc(bsdtar, 1, 0, "Invalid regular expression: %s", buf); + } + free(pattern); + + start_subst = end_pattern + 1; + end_pattern = strchr(start_subst, *rule_text); + if (end_pattern == NULL) + bsdtar_errc(bsdtar, 1, 0, "Invalid replacement string"); + + rule->result = malloc(end_pattern - start_subst + 1); + if (rule->result == NULL) + bsdtar_errc(bsdtar, 1, errno, "Out of memory"); + memcpy(rule->result, start_subst, end_pattern - start_subst); + rule->result[end_pattern - start_subst] = '\0'; + + rule->global = 0; + rule->print = 0; + rule->symlink = 0; + + while (*++end_pattern) { + switch (*end_pattern) { + case 'g': + case 'G': + rule->global = 1; + break; + case 'p': + case 'P': + rule->print = 1; + break; + case 's': + case 'S': + rule->symlink = 1; + break; + default: + bsdtar_errc(bsdtar, 1, 0, "Invalid replacement flag %c", *end_pattern); + } + } +} + +static void +realloc_strncat(struct bsdtar *bsdtar, char **str, const char *append, size_t len) +{ + char *new_str; + size_t old_len; + + if (*str == NULL) + old_len = 0; + else + old_len = strlen(*str); + + new_str = malloc(old_len + len + 1); + if (new_str == NULL) + bsdtar_errc(bsdtar, 1, errno, "Out of memory"); + memcpy(new_str, *str, old_len); + memcpy(new_str + old_len, append, len); + new_str[old_len + len] = '\0'; + free(*str); + *str = new_str; +} + +static void +realloc_strcat(struct bsdtar *bsdtar, char **str, const char *append) +{ + char *new_str; + size_t old_len; + + if (*str == NULL) + old_len = 0; + else + old_len = strlen(*str); + + new_str = malloc(old_len + strlen(append) + 1); + if (new_str == NULL) + bsdtar_errc(bsdtar, 1, errno, "Out of memory"); + memcpy(new_str, *str, old_len); + strcpy(new_str + old_len, append); + free(*str); + *str = new_str; +} + +int +apply_substitution(struct bsdtar *bsdtar, const char *name, char **result, int symlink_only) +{ + const char *path = name; + regmatch_t matches[10]; + size_t i, j; + struct subst_rule *rule; + struct substitution *subst; + int c, got_match, print_match; + + *result = NULL; + + if ((subst = bsdtar->substitution) == NULL) + return 0; + + got_match = 0; + print_match = 0; + + for (rule = subst->first_rule; rule != NULL; rule = rule->next) { + if (symlink_only && !rule->symlink) + continue; + if (regexec(&rule->re, name, 10, matches, 0)) + break; + + got_match = 1; + print_match |= rule->print; + realloc_strncat(bsdtar, result, name, matches[0].rm_so); + + for (i = 0, j = 0; rule->result[i] != '\0'; ++i) { + if (rule->result[i] == '~') { + realloc_strncat(bsdtar, result, rule->result + j, i - j); + realloc_strncat(bsdtar, result, name, matches[0].rm_eo); + j = i + 1; + continue; + } + if (rule->result[i] != '\\') + continue; + + ++i; + c = rule->result[i]; + switch (c) { + case '~': + case '\\': + realloc_strncat(bsdtar, result, rule->result + j, i - j - 1); + j = i; + break; + case '1' ... '9': + realloc_strncat(bsdtar, result, rule->result + j, i - j - 1); + if ((size_t)(c - '0') > (size_t)(rule->re.re_nsub)) { + free(*result); + *result = NULL; + return -1; + } + realloc_strncat(bsdtar, result, name + matches[c - '0'].rm_so, matches[c - '0'].rm_eo - matches[c - '0'].rm_so); + j = i + 1; + break; + default: + /* Just continue; */ + break; + } + + } + + realloc_strcat(bsdtar, result, rule->result + j); + + name += matches[0].rm_eo; + + if (!rule->global) + break; + } + + if (got_match) + realloc_strcat(bsdtar, result, name); + + if (print_match) + fprintf(stderr, "%s >> %s\n", path, *result); + + return got_match; +} + +void +cleanup_substitution(struct bsdtar *bsdtar) +{ + struct subst_rule *rule; + struct substitution *subst; + + if ((subst = bsdtar->substitution) == NULL) + return; + + while ((rule = subst->first_rule) != NULL) { + subst->first_rule = rule->next; + free(rule->result); + free(rule); + } + free(subst); +} +#endif /* HAVE_REGEX_H */ diff --git a/usr.bin/tar/test/Makefile b/usr.bin/tar/test/Makefile index 485669aec77..c74346c410a 100644 --- a/usr.bin/tar/test/Makefile +++ b/usr.bin/tar/test/Makefile @@ -15,6 +15,7 @@ TESTS= \ test_getdate.c \ test_help.c \ test_option_T.c \ + test_patterns.c \ test_stdio.c \ test_version.c diff --git a/usr.bin/tar/test/test_copy.c b/usr.bin/tar/test/test_copy.c index e0bbb064a99..52b9b786673 100644 --- a/usr.bin/tar/test/test_copy.c +++ b/usr.bin/tar/test/test_copy.c @@ -35,11 +35,16 @@ create_tree(void) assertEqualInt(0, mkdir("original", 0775)); chdir("original"); + assertEqualInt(0, mkdir("f", 0775)); + assertEqualInt(0, mkdir("l", 0775)); + assertEqualInt(0, mkdir("m", 0775)); + assertEqualInt(0, mkdir("s", 0775)); + assertEqualInt(0, mkdir("d", 0775)); - buff[0] = 'f'; - buff[1] = '_'; for (i = 0; i < 200; i++) { - /* Create a file named "f_abcdef..." */ + buff[0] = 'f'; + buff[1] = '/'; + /* Create a file named "f/abcdef..." */ buff[i + 2] = 'a' + (i % 26); buff[i + 3] = '\0'; fd = open(buff, O_CREAT | O_WRONLY, 0644); @@ -47,23 +52,27 @@ create_tree(void) assertEqualInt(i + 3, write(fd, buff, strlen(buff))); close(fd); - /* Create a link named "l_abcdef..." to the above. */ + /* Create a link named "l/abcdef..." to the above. */ strcpy(buff2, buff); buff2[0] = 'l'; assertEqualInt(0, link(buff, buff2)); - /* Create a link named "m_abcdef..." to the above. */ + /* Create a link named "m/abcdef..." to the above. */ strcpy(buff2, buff); buff2[0] = 'm'; assertEqualInt(0, link(buff, buff2)); - /* Create a symlink named "s_abcdef..." to the above. */ - buff2[0] = 's'; - assertEqualInt(0, symlink(buff, buff2)); - - /* Create a dir named "d_abcdef...". */ - buff2[0] = 'd'; - assertEqualInt(0, mkdir(buff2, 0775)); + /* Create a symlink named "s/abcdef..." to the above. */ + strcpy(buff2 + 3, buff); + buff[0] = 's'; + buff2[0] = '.'; + buff2[1] = '.'; + buff2[2] = '/'; + assertEqualInt(0, symlink(buff2, buff)); + + /* Create a dir named "d/abcdef...". */ + buff[0] = 'd'; + assertEqualInt(0, mkdir(buff, 0775)); } chdir(".."); @@ -77,35 +86,44 @@ verify_tree(int limit) { struct stat st, st2; char filename[260]; + char name1[260]; + char name2[260]; char contents[260]; - int i, r; + int i, j, r; int fd; int len; - const char *p; + const char *p, *dp; DIR *d; struct dirent *de; /* Generate the names we know should be there and verify them. */ - for (i = 0; i < 200; i++) { - /* Verify a file named "f_abcdef..." */ - filename[0] = 'f'; - filename[1] = '_'; - filename[i + 2] = 'a' + (i % 26); - filename[i + 3] = '\0'; + for (i = 1; i < 200; i++) { + /* Generate a base name of the correct length. */ + for (j = 0; j < i; ++j) + filename[j] = 'a' + (j % 26); +#if 0 + for (n = i; n > 0; n /= 10) + filename[--j] = '0' + (n % 10); +#endif + filename[i] = '\0'; + + /* Verify a file named "f/abcdef..." */ + strcpy(name1, "f/"); + strcat(name1, filename); if (limit != LIMIT_USTAR || strlen(filename) <= 100) { - fd = open(filename, O_RDONLY); + fd = open(name1, O_RDONLY); failure("Couldn't open \"%s\": %s", - filename, strerror(errno)); + name1, strerror(errno)); if (assert(fd >= 0)) { len = read(fd, contents, i + 10); close(fd); - assertEqualInt(len, i + 3); + assertEqualInt(len, i + 2); /* Verify contents of 'contents' */ contents[len] = '\0'; failure("Each test file contains its own name"); - assertEqualString(filename, contents); - /* stat() file and get dev/ino for next check */ - assertEqualInt(0, lstat(filename, &st)); + assertEqualString(name1, contents); + /* stat() for dev/ino for next check */ + assertEqualInt(0, lstat(name1, &st)); } } @@ -114,18 +132,19 @@ verify_tree(int limit) * "original/" as part of the name, so the link * names here can't exceed 91 chars. */ - if (limit != LIMIT_USTAR || strlen(filename) <= 91) { - /* Verify hardlink "l_abcdef..." */ - filename[0] = 'l'; - assertEqualInt(0, (r = lstat(filename, &st2))); + strcpy(name2, "l/"); + strcat(name2, filename); + if (limit != LIMIT_USTAR || strlen(name2) <= 100) { + /* Verify hardlink "l/abcdef..." */ + assertEqualInt(0, (r = lstat(name2, &st2))); if (r == 0) { assertEqualInt(st2.st_dev, st.st_dev); assertEqualInt(st2.st_ino, st.st_ino); } /* Verify hardlink "m_abcdef..." */ - filename[0] = 'm'; - assertEqualInt(0, (r = lstat(filename, &st2))); + name2[0] = 'm'; + assertEqualInt(0, (r = lstat(name2, &st2))); if (r == 0) { assertEqualInt(st2.st_dev, st.st_dev); assertEqualInt(st2.st_ino, st.st_ino); @@ -136,30 +155,32 @@ verify_tree(int limit) * Symlink text doesn't include the 'original/' prefix, * so the limit here is 100 characters. */ - /* Verify symlink "s_abcdef..." */ - filename[0] = 's'; - if (limit != LIMIT_USTAR || strlen(filename) <= 100) { + /* Verify symlink "s/abcdef..." */ + strcpy(name2, "../s/"); + strcat(name2, filename); + if (limit != LIMIT_USTAR || strlen(name2) <= 100) { /* This is a symlink. */ failure("Couldn't stat %s (length %d)", filename, strlen(filename)); - if (assertEqualInt(0, lstat(filename, &st2))) { + if (assertEqualInt(0, lstat(name2 + 3, &st2))) { assert(S_ISLNK(st2.st_mode)); /* This is a symlink to the file above. */ - failure("Couldn't stat %s", filename); - if (assertEqualInt(0, stat(filename, &st2))) { + failure("Couldn't stat %s", name2 + 3); + if (assertEqualInt(0, stat(name2 + 3, &st2))) { assertEqualInt(st2.st_dev, st.st_dev); assertEqualInt(st2.st_ino, st.st_ino); } } } - /* Verify dir "d_abcdef...". */ - filename[0] = 'd'; + /* Verify dir "d/abcdef...". */ + strcpy(name1, "d/"); + strcat(name1, filename); if (limit != LIMIT_USTAR || strlen(filename) < 100) { /* This is a dir. */ failure("Couldn't stat %s (length %d)", - filename, strlen(filename)); - if (assertEqualInt(0, lstat(filename, &st2))) { + name1, strlen(filename)); + if (assertEqualInt(0, lstat(name1, &st2))) { if (assert(S_ISDIR(st2.st_mode))) { /* TODO: opendir/readdir this * directory and make sure @@ -171,43 +192,47 @@ verify_tree(int limit) } /* Now make sure nothing is there that shouldn't be. */ - d = opendir("."); - while ((de = readdir(d)) != NULL) { - p = de->d_name; - switch(p[0]) { - case 'l': case 'm': - if (limit == LIMIT_USTAR) { - failure("strlen(p) = %d", strlen(p)); - assert(strlen(p) < 92); - } - case 'd': - if (limit == LIMIT_USTAR) { - failure("strlen(p)=%d", strlen(p)); - assert(strlen(p) < 100); - } - case 'f': case 's': - if (limit == LIMIT_USTAR) { - failure("strlen(p)=%d", strlen(p)); - assert(strlen(p) < 101); + for (dp = "dflms"; *dp != '\0'; ++dp) { + char dir[2]; + dir[0] = *dp; dir[1] = '\0'; + d = opendir(dir); + while ((de = readdir(d)) != NULL) { + p = de->d_name; + switch(dp[0]) { + case 'l': case 'm': + if (limit == LIMIT_USTAR) { + failure("strlen(p) = %d", strlen(p)); + assert(strlen(p) <= 100); + } + case 'd': + if (limit == LIMIT_USTAR) { + failure("strlen(p)=%d", strlen(p)); + assert(strlen(p) < 100); + } + case 'f': case 's': + if (limit == LIMIT_USTAR) { + failure("strlen(p)=%d", strlen(p)); + assert(strlen(p) < 101); + } + /* Our files have very particular filename patterns. */ + if (p[0] != '.' || (p[1] != '.' && p[1] != '\0')) { + for (i = 0; p[i] != '\0' && i < 200; i++) { + failure("i=%d, p[i]='%c' 'a'+(i%%26)='%c'", i, p[i], 'a' + (i % 26)); + assertEqualInt(p[i], 'a' + (i % 26)); + } + assert(p[i] == '\0'); + } + break; + case '.': + assert(p[1] == '\0' || (p[1] == '.' && p[2] == '\0')); + break; + default: + failure("File %s shouldn't be here", p); + assert(0); } - /* Our files have very particular filename patterns. */ - assert(p[1] == '_' && p[2] == 'a'); - assert(p[2] == 'a'); - p += 2; - for (i = 0; p[i] != '\0' && i < 200; i++) - assert(p[i] == 'a' + (i % 26)); - assert(p[i] == '\0'); - break; - case '.': - assert(p[1] == '\0' || (p[1] == '.' && p[2] == '\0')); - break; - default: - failure("File %s shouldn't be here", p); - assert(0); } - + closedir(d); } - closedir(d); } static void @@ -216,12 +241,12 @@ copy_basic(void) int r; assertEqualInt(0, mkdir("plain", 0775)); - chdir("plain"); + assertEqualInt(0, chdir("plain")); /* * Use the tar program to create an archive. */ - r = systemf("%s cf archive -C .. original >pack.out 2>pack.err", + r = systemf("%s cf archive -C ../original f d l m s >pack.out 2>pack.err", testprog); failure("Error invoking \"%s cf\"", testprog); assertEqualInt(r, 0); @@ -241,9 +266,8 @@ copy_basic(void) assertEmptyFile("unpack.err"); assertEmptyFile("unpack.out"); - chdir("original"); verify_tree(LIMIT_NONE); - chdir("../.."); + assertEqualInt(0, chdir("..")); } static void @@ -253,18 +277,20 @@ copy_ustar(void) int r; assertEqualInt(0, mkdir(target, 0775)); - chdir(target); + assertEqualInt(0, chdir(target)); /* * Use the tar program to create an archive. */ - r = systemf("%s cf archive --format=ustar -C .. original >pack.out 2>pack.err", + r = systemf("%s cf archive --format=ustar -C ../original f d l m s >pack.out 2>pack.err", testprog); failure("Error invoking \"%s cf archive --format=ustar\"", testprog); assertEqualInt(r, 0); /* Verify that nothing went to stdout. */ assertEmptyFile("pack.out"); + /* Stderr is non-empty, since there are a bunch of files + * with filenames too long to archive. */ /* * Use tar to unpack the archive into another directory. diff --git a/usr.bin/tar/test/test_option_T.c b/usr.bin/tar/test/test_option_T.c index 7b579085d5c..eec40bb4587 100644 --- a/usr.bin/tar/test/test_option_T.c +++ b/usr.bin/tar/test/test_option_T.c @@ -118,6 +118,25 @@ DEFINE_TEST(test_option_T) assertFileExists("test2/d1/d2/f4"); assertFileNotExists("test2/d1/d2/f5"); + assertEqualInt(0, mkdir("test4", 0755)); + assertEqualInt(0, mkdir("test4_out", 0755)); + assertEqualInt(0, mkdir("test4_out2", 0755)); + assertEqualInt(0, mkdir("test4/d1", 0755)); + assertEqualInt(1, touch("test4/d1/foo")); + + systemf("%s -cf - -s /foo/bar/ test4/d1/foo | %s -xf - -C test4_out", + testprog, testprog); + assertEmptyFile("test4_out/test4/d1/bar"); + systemf("%s -cf - -s /d1/d2/ test4/d1/foo | %s -xf - -C test4_out", + testprog, testprog); + assertEmptyFile("test4_out/test4/d2/foo"); + systemf("%s -cf - -s ,test4/d1/foo,, test4/d1/foo | %s -tvf - > test4.lst", + testprog, testprog); + assertEmptyFile("test4.lst"); + systemf("%s -cf - test4/d1/foo | %s -xf - -s /foo/bar/ -C test4_out2", + testprog, testprog); + assertEmptyFile("test4_out2/test4/d1/bar"); + /* TODO: Include some use of -C directory-changing within the filelist. */ /* I'm pretty sure -C within the filelist is broken on extract. */ } diff --git a/usr.bin/tar/test/test_patterns.c b/usr.bin/tar/test/test_patterns.c new file mode 100644 index 00000000000..e7b1679fd9d --- /dev/null +++ b/usr.bin/tar/test/test_patterns.c @@ -0,0 +1,47 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD$"); + +DEFINE_TEST(test_patterns) +{ + int fd, r; + + /* + * Test basic command-line pattern handling. + */ + + /* + * John Baldwin reported this problem in PR bin/121598 + */ + fd = open("foo", O_CREAT | O_WRONLY, 0644); + assert(fd >= 0); + close(fd); + r = systemf("%s zcfv tar1.tgz foo > tar1a.out 2> tar1a.err", testprog); + assertEqualInt(r, 0); + r = systemf("%s zxfv tar1.tgz foo bar > tar1b.out 2> tar1b.err", testprog); + failure("tar should return non-zero because a file was given on the command line that's not in the archive"); + assert(r != 0); +} diff --git a/usr.bin/tar/util.c b/usr.bin/tar/util.c index e05e52c3993..4ddcb0082b3 100644 --- a/usr.bin/tar/util.c +++ b/usr.bin/tar/util.c @@ -178,7 +178,7 @@ yes(const char *fmt, ...) fprintf(stderr, " (y/N)? "); fflush(stderr); - l = read(2, buff, sizeof(buff)); + l = read(2, buff, sizeof(buff) - 1); if (l <= 0) return (0); buff[l] = 0; @@ -215,7 +215,7 @@ process_lines(struct bsdtar *bsdtar, const char *pathname, { FILE *f; char *buff, *buff_end, *line_start, *line_end, *p; - size_t buff_length, bytes_read, bytes_wanted; + size_t buff_length, new_buff_length, bytes_read, bytes_wanted; int separator; int ret; @@ -262,7 +262,12 @@ process_lines(struct bsdtar *bsdtar, const char *pathname, line_start = buff; } else { /* Line is too big; enlarge the buffer. */ - p = realloc(buff, buff_length *= 2); + new_buff_length = buff_length * 2; + if (new_buff_length <= buff_length) + bsdtar_errc(bsdtar, 1, ENOMEM, + "Line too long in %s", pathname); + buff_length = new_buff_length; + p = realloc(buff, buff_length); if (p == NULL) bsdtar_errc(bsdtar, 1, ENOMEM, "Line too long in %s", pathname); @@ -351,10 +356,51 @@ int edit_pathname(struct bsdtar *bsdtar, struct archive_entry *entry) { const char *name = archive_entry_pathname(entry); +#if HAVE_REGEX_H + char *subst_name; +#endif + int r; + +#if HAVE_REGEX_H + r = apply_substitution(bsdtar, name, &subst_name, 0); + if (r == -1) { + bsdtar_warnc(bsdtar, 0, "Invalid substituion, skipping entry"); + return 1; + } + if (r == 1) { + archive_entry_copy_pathname(entry, subst_name); + free(subst_name); + if (*subst_name == '\0') + return -1; + name = archive_entry_pathname(entry); + } + + if (archive_entry_hardlink(entry)) { + r = apply_substitution(bsdtar, archive_entry_hardlink(entry), &subst_name, 1); + if (r == -1) { + bsdtar_warnc(bsdtar, 0, "Invalid substituion, skipping entry"); + return 1; + } + if (r == 1) { + archive_entry_copy_hardlink(entry, subst_name); + free(subst_name); + } + } + if (archive_entry_symlink(entry) != NULL) { + r = apply_substitution(bsdtar, archive_entry_symlink(entry), &subst_name, 1); + if (r == -1) { + bsdtar_warnc(bsdtar, 0, "Invalid substituion, skipping entry"); + return 1; + } + if (r == 1) { + archive_entry_copy_symlink(entry, subst_name); + free(subst_name); + } + } +#endif /* Strip leading dir names as per --strip-components option. */ - if (bsdtar->strip_components > 0) { - int r = bsdtar->strip_components; + if ((r = bsdtar->strip_components) > 0) { const char *p = name; while (r > 0) { @@ -368,6 +414,10 @@ edit_pathname(struct bsdtar *bsdtar, struct archive_entry *entry) return (1); } } + while (*name == '/') + ++name; + if (*name == '\0') + return (1); } /* Strip redundant leading '/' characters. */ diff --git a/usr.bin/tar/write.c b/usr.bin/tar/write.c index 9b3f39bc1c1..f2ff46a5479 100644 --- a/usr.bin/tar/write.c +++ b/usr.bin/tar/write.c @@ -87,9 +87,6 @@ __FBSDID("$FreeBSD$"); static const char * const NO_NAME = "(noname)"; -/* Initial size of link cache. */ -#define links_cache_initial_size 1024 - struct archive_dir_entry { struct archive_dir_entry *next; time_t mtime_sec; @@ -101,21 +98,6 @@ struct archive_dir { struct archive_dir_entry *head, *tail; }; -struct links_cache { - unsigned long number_entries; - size_t number_buckets; - struct links_entry **buckets; -}; - -struct links_entry { - struct links_entry *next; - struct links_entry *previous; - int links; - dev_t dev; - ino_t ino; - char *name; -}; - struct name_cache { int probes; int hits; @@ -139,13 +121,10 @@ static int archive_names_from_file_helper(struct bsdtar *bsdtar, static int copy_file_data(struct bsdtar *bsdtar, struct archive *a, struct archive *ina); static void create_cleanup(struct bsdtar *); -static void free_buckets(struct bsdtar *, struct links_cache *); static void free_cache(struct name_cache *cache); static const char * lookup_gname(struct bsdtar *bsdtar, gid_t gid); static int lookup_gname_helper(struct bsdtar *bsdtar, const char **name, id_t gid); -static void lookup_hardlink(struct bsdtar *, - struct archive_entry *entry, const struct stat *); static const char * lookup_uname(struct bsdtar *bsdtar, uid_t uid); static int lookup_uname_helper(struct bsdtar *bsdtar, const char **name, id_t uid); @@ -160,6 +139,8 @@ static void write_archive(struct archive *, struct bsdtar *); static void write_entry(struct bsdtar *, struct archive *, const struct stat *, const char *pathname, const char *accpath); +static void write_entry_backend(struct bsdtar *, struct archive *, + struct archive_entry *, int); static int write_file_data(struct bsdtar *, struct archive *, int fd); static void write_hierarchy(struct bsdtar *, struct archive *, @@ -266,6 +247,9 @@ tar_mode_r(struct bsdtar *bsdtar) /* Sanity-test some arguments and the file. */ test_for_append(bsdtar); + /* We want to catch SIGINFO and SIGUSR1. */ + siginfo_init(bsdtar); + format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED; bsdtar->fd = open(bsdtar->filename, O_RDWR | O_CREAT, 0666); @@ -364,6 +348,9 @@ tar_mode_u(struct bsdtar *bsdtar) /* Sanity-test some arguments and the file. */ test_for_append(bsdtar); + /* We want to catch SIGINFO and SIGUSR1. */ + siginfo_init(bsdtar); + bsdtar->fd = open(bsdtar->filename, O_RDWR); if (bsdtar->fd < 0) bsdtar_errc(bsdtar, 1, errno, @@ -448,6 +435,12 @@ static void write_archive(struct archive *a, struct bsdtar *bsdtar) { const char *arg; + struct archive_entry *entry, *sparse_entry; + + if ((bsdtar->resolver = archive_entry_linkresolver_new()) == NULL) + bsdtar_errc(bsdtar, 1, 0, "cannot create link resolver"); + archive_entry_linkresolver_set_strategy(bsdtar->resolver, + archive_format(a)); if (bsdtar->names_from_file != NULL) archive_names_from_file(bsdtar, a); @@ -480,6 +473,16 @@ write_archive(struct archive *a, struct bsdtar *bsdtar) bsdtar->argv++; } + entry = NULL; + archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry); + while (entry != NULL) { + int fd = -1; + write_entry_backend(bsdtar, a, entry, fd); + archive_entry_free(entry); + entry = NULL; + archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry); + } + create_cleanup(bsdtar); if (archive_write_close(a)) { bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); @@ -597,9 +600,12 @@ append_archive(struct bsdtar *bsdtar, struct archive *a, struct archive *ina) if (e == ARCHIVE_FATAL) exit(1); - if (e >= ARCHIVE_WARN) - if (copy_file_data(bsdtar, a, ina)) + if (e >= ARCHIVE_WARN) { + if (archive_entry_size(in_entry) == 0) + archive_read_data_skip(ina); + else if (copy_file_data(bsdtar, a, ina)) exit(1); + } if (bsdtar->verbose) fprintf(stderr, "\n"); @@ -790,6 +796,54 @@ write_hierarchy(struct bsdtar *bsdtar, struct archive *a, const char *path) tree_close(tree); } +/* + * Backend for write_entry. + */ +static void +write_entry_backend(struct bsdtar *bsdtar, struct archive *a, + struct archive_entry *entry, int fd) +{ + int e; + + if (fd == -1 && archive_entry_size(entry) > 0) { + const char *pathname = archive_entry_sourcepath(entry); + fd = open(pathname, O_RDONLY); + if (fd == -1) { + if (!bsdtar->verbose) + bsdtar_warnc(bsdtar, errno, + "%s: could not open file", pathname); + else + fprintf(stderr, ": %s", strerror(errno)); + return; + } + } + + e = archive_write_header(a, entry); + if (e != ARCHIVE_OK) { + if (!bsdtar->verbose) + bsdtar_warnc(bsdtar, 0, "%s: %s", + archive_entry_pathname(entry), + archive_error_string(a)); + else + fprintf(stderr, ": %s", archive_error_string(a)); + } + + if (e == ARCHIVE_FATAL) + exit(1); + + /* + * If we opened a file earlier, write it out now. Note that + * the format handler might have reset the size field to zero + * to inform us that the archive body won't get stored. In + * that case, just skip the write. + */ + if (e >= ARCHIVE_WARN && fd >= 0 && archive_entry_size(entry) > 0) { + if (write_file_data(bsdtar, a, fd)) + exit(1); + close(fd); + } +} + /* * Add a single filesystem object to the archive. */ @@ -797,9 +851,8 @@ static void write_entry(struct bsdtar *bsdtar, struct archive *a, const struct stat *st, const char *pathname, const char *accpath) { - struct archive_entry *entry; - int e; - int fd; + struct archive_entry *entry, *sparse_entry; + int fd; #ifdef __linux int r; unsigned long stflags; @@ -810,6 +863,7 @@ write_entry(struct bsdtar *bsdtar, struct archive *a, const struct stat *st, entry = archive_entry_new(); archive_entry_set_pathname(entry, pathname); + archive_entry_copy_sourcepath(entry, accpath); /* * Rewrite the pathname to be archived. If rewrite @@ -825,9 +879,6 @@ write_entry(struct bsdtar *bsdtar, struct archive *a, const struct stat *st, if (!new_enough(bsdtar, archive_entry_pathname(entry), st)) goto abort; - if (!S_ISDIR(st->st_mode) && (st->st_nlink > 1)) - lookup_hardlink(bsdtar, entry, st); - /* Display entry as we process it. This format is required by SUSv2. */ if (bsdtar->verbose) safe_fprintf(stderr, "a %s", archive_entry_pathname(entry)); @@ -874,23 +925,6 @@ write_entry(struct bsdtar *bsdtar, struct archive *a, const struct stat *st, setup_acls(bsdtar, entry, accpath); setup_xattrs(bsdtar, entry, accpath); - /* - * If it's a regular file (and non-zero in size) make sure we - * can open it before we start to write. In particular, note - * that we can always archive a zero-length file, even if we - * can't read it. - */ - if (S_ISREG(st->st_mode) && st->st_size > 0) { - fd = open(accpath, O_RDONLY); - if (fd < 0) { - if (!bsdtar->verbose) - bsdtar_warnc(bsdtar, errno, "%s: could not open file", pathname); - else - fprintf(stderr, ": %s", strerror(errno)); - goto cleanup; - } - } - /* Non-regular files get archived with zero size. */ if (!S_ISREG(st->st_mode)) archive_entry_set_size(entry, 0); @@ -898,32 +932,19 @@ write_entry(struct bsdtar *bsdtar, struct archive *a, const struct stat *st, /* Record what we're doing, for the benefit of SIGINFO / SIGUSR1. */ siginfo_setinfo(bsdtar, "adding", archive_entry_pathname(entry), archive_entry_size(entry)); + archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry); /* Handle SIGINFO / SIGUSR1 request if one was made. */ siginfo_printinfo(bsdtar, 0); - e = archive_write_header(a, entry); - if (e != ARCHIVE_OK) { - if (!bsdtar->verbose) - bsdtar_warnc(bsdtar, 0, "%s: %s", pathname, - archive_error_string(a)); - else - fprintf(stderr, ": %s", archive_error_string(a)); + while (entry != NULL) { + write_entry_backend(bsdtar, a, entry, fd); + fd = -1; + archive_entry_free(entry); + entry = sparse_entry; + sparse_entry = NULL; } - if (e == ARCHIVE_FATAL) - exit(1); - - /* - * If we opened a file earlier, write it out now. Note that - * the format handler might have reset the size field to zero - * to inform us that the archive body won't get stored. In - * that case, just skip the write. - */ - if (e >= ARCHIVE_WARN && fd >= 0 && archive_entry_size(entry) > 0) - if (write_file_data(bsdtar, a, fd)) - exit(1); - cleanup: if (bsdtar->verbose) fprintf(stderr, "\n"); @@ -974,168 +995,12 @@ write_file_data(struct bsdtar *bsdtar, struct archive *a, int fd) static void create_cleanup(struct bsdtar *bsdtar) { - /* Free inode->pathname map used for hardlink detection. */ - if (bsdtar->links_cache != NULL) { - free_buckets(bsdtar, bsdtar->links_cache); - free(bsdtar->links_cache); - bsdtar->links_cache = NULL; - } - free_cache(bsdtar->uname_cache); bsdtar->uname_cache = NULL; free_cache(bsdtar->gname_cache); bsdtar->gname_cache = NULL; } - -static void -free_buckets(struct bsdtar *bsdtar, struct links_cache *links_cache) -{ - size_t i; - - if (links_cache->buckets == NULL) - return; - - for (i = 0; i < links_cache->number_buckets; i++) { - while (links_cache->buckets[i] != NULL) { - struct links_entry *lp = links_cache->buckets[i]->next; - if (bsdtar->option_warn_links) - bsdtar_warnc(bsdtar, 0, "Missing links to %s", - links_cache->buckets[i]->name); - if (links_cache->buckets[i]->name != NULL) - free(links_cache->buckets[i]->name); - free(links_cache->buckets[i]); - links_cache->buckets[i] = lp; - } - } - free(links_cache->buckets); - links_cache->buckets = NULL; -} - -static void -lookup_hardlink(struct bsdtar *bsdtar, struct archive_entry *entry, - const struct stat *st) -{ - struct links_cache *links_cache; - struct links_entry *le, **new_buckets; - int hash; - size_t i, new_size; - - /* If necessary, initialize the links cache. */ - links_cache = bsdtar->links_cache; - if (links_cache == NULL) { - bsdtar->links_cache = malloc(sizeof(struct links_cache)); - if (bsdtar->links_cache == NULL) - bsdtar_errc(bsdtar, 1, ENOMEM, - "No memory for hardlink detection."); - links_cache = bsdtar->links_cache; - memset(links_cache, 0, sizeof(struct links_cache)); - links_cache->number_buckets = links_cache_initial_size; - links_cache->buckets = malloc(links_cache->number_buckets * - sizeof(links_cache->buckets[0])); - if (links_cache->buckets == NULL) { - bsdtar_errc(bsdtar, 1, ENOMEM, - "No memory for hardlink detection."); - } - for (i = 0; i < links_cache->number_buckets; i++) - links_cache->buckets[i] = NULL; - } - - /* If the links cache overflowed and got flushed, don't bother. */ - if (links_cache->buckets == NULL) - return; - - /* If the links cache is getting too full, enlarge the hash table. */ - if (links_cache->number_entries > links_cache->number_buckets * 2) - { - new_size = links_cache->number_buckets * 2; - new_buckets = malloc(new_size * sizeof(struct links_entry *)); - - if (new_buckets != NULL) { - memset(new_buckets, 0, - new_size * sizeof(struct links_entry *)); - for (i = 0; i < links_cache->number_buckets; i++) { - while (links_cache->buckets[i] != NULL) { - /* Remove entry from old bucket. */ - le = links_cache->buckets[i]; - links_cache->buckets[i] = le->next; - - /* Add entry to new bucket. */ - hash = (le->dev ^ le->ino) % new_size; - - if (new_buckets[hash] != NULL) - new_buckets[hash]->previous = - le; - le->next = new_buckets[hash]; - le->previous = NULL; - new_buckets[hash] = le; - } - } - free(links_cache->buckets); - links_cache->buckets = new_buckets; - links_cache->number_buckets = new_size; - } else { - free_buckets(bsdtar, links_cache); - bsdtar_warnc(bsdtar, ENOMEM, - "No more memory for recording hard links"); - bsdtar_warnc(bsdtar, 0, - "Remaining links will be dumped as full files"); - } - } - - /* Try to locate this entry in the links cache. */ - hash = ( st->st_dev ^ st->st_ino ) % links_cache->number_buckets; - for (le = links_cache->buckets[hash]; le != NULL; le = le->next) { - if (le->dev == st->st_dev && le->ino == st->st_ino) { - archive_entry_copy_hardlink(entry, le->name); - - /* - * Decrement link count each time and release - * the entry if it hits zero. This saves - * memory and is necessary for proper -l - * implementation. - */ - if (--le->links <= 0) { - if (le->previous != NULL) - le->previous->next = le->next; - if (le->next != NULL) - le->next->previous = le->previous; - free(le->name); - if (links_cache->buckets[hash] == le) - links_cache->buckets[hash] = le->next; - links_cache->number_entries--; - free(le); - } - - return; - } - } - - /* Add this entry to the links cache. */ - le = malloc(sizeof(struct links_entry)); - if (le != NULL) - le->name = strdup(archive_entry_pathname(entry)); - if ((le == NULL) || (le->name == NULL)) { - free_buckets(bsdtar, links_cache); - bsdtar_warnc(bsdtar, ENOMEM, - "No more memory for recording hard links"); - bsdtar_warnc(bsdtar, 0, - "Remaining hard links will be dumped as full files"); - if (le != NULL) - free(le); - return; - } - if (links_cache->buckets[hash] != NULL) - links_cache->buckets[hash]->previous = le; - links_cache->number_entries++; - le->next = links_cache->buckets[hash]; - le->previous = NULL; - links_cache->buckets[hash] = le; - le->dev = st->st_dev; - le->ino = st->st_ino; - le->links = st->st_nlink - 1; -} - #ifdef HAVE_POSIX_ACL static void setup_acl(struct bsdtar *bsdtar, struct archive_entry *entry, const char *accpath, -- 2.45.0