diff --git a/usr.bin/tar/bsdtar.c b/usr.bin/tar/bsdtar.c index 24aaad7e658d..af762eacdbeb 100644 --- a/usr.bin/tar/bsdtar.c +++ b/usr.bin/tar/bsdtar.c @@ -1,663 +1,678 @@ /*- * Copyright (c) 2003-2008 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 "bsdtar_platform.h" __FBSDID("$FreeBSD$"); #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_LANGINFO_H #include #endif #ifdef HAVE_LOCALE_H #include #endif #ifdef HAVE_PATHS_H #include #endif #include #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_TIME_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #if HAVE_ZLIB_H #include #endif #include "bsdtar.h" /* * Per POSIX.1-1988, tar defaults to reading/writing archives to/from * the default tape device for the system. Pick something reasonable here. */ #ifdef __linux #define _PATH_DEFTAPE "/dev/st0" #endif +#ifdef _WIN32 +#define _PATH_DEFTAPE "\\\\.\\tape0" +#endif #ifndef _PATH_DEFTAPE #define _PATH_DEFTAPE "/dev/tape" #endif /* External function to parse a date/time string (from getdate.y) */ time_t get_date(const char *); static void long_help(struct bsdtar *); static void only_mode(struct bsdtar *, const char *opt, const char *valid); static void set_mode(struct bsdtar *, char opt); static void version(void); /* A basic set of security flags to request from libarchive. */ #define SECURITY \ (ARCHIVE_EXTRACT_SECURE_SYMLINKS \ | ARCHIVE_EXTRACT_SECURE_NODOTDOT) int main(int argc, char **argv) { struct bsdtar *bsdtar, bsdtar_storage; int opt, t; char option_o; char possible_help_request; char buff[16]; /* * Use a pointer for consistency, but stack-allocated storage * for ease of cleanup. */ bsdtar = &bsdtar_storage; memset(bsdtar, 0, sizeof(*bsdtar)); bsdtar->fd = -1; /* Mark as "unused" */ option_o = 0; +#ifdef _WIN32 + /* open() function is always with a binary mode. */ + _set_fmode(_O_BINARY); +#endif /* Need bsdtar->progname before calling bsdtar_warnc. */ if (*argv == NULL) bsdtar->progname = "bsdtar"; else { +#if _WIN32 + bsdtar->progname = strrchr(*argv, '\\'); +#else bsdtar->progname = strrchr(*argv, '/'); +#endif if (bsdtar->progname != NULL) bsdtar->progname++; else bsdtar->progname = *argv; } if (setlocale(LC_ALL, "") == NULL) bsdtar_warnc(bsdtar, 0, "Failed to set default locale"); #if defined(HAVE_NL_LANGINFO) && defined(HAVE_D_MD_ORDER) bsdtar->day_first = (*nl_langinfo(D_MD_ORDER) == 'd'); #endif possible_help_request = 0; /* Look up uid of current user for future reference */ bsdtar->user_uid = geteuid(); /* Default: open tape drive. */ bsdtar->filename = getenv("TAPE"); if (bsdtar->filename == NULL) bsdtar->filename = _PATH_DEFTAPE; /* Default: preserve mod time on extract */ bsdtar->extract_flags = ARCHIVE_EXTRACT_TIME; /* Default: Perform basic security checks. */ bsdtar->extract_flags |= SECURITY; /* Defaults for root user: */ - if (bsdtar->user_uid == 0) { + if (bsdtar_is_privileged(bsdtar)) { /* --same-owner */ bsdtar->extract_flags |= ARCHIVE_EXTRACT_OWNER; /* -p */ bsdtar->extract_flags |= ARCHIVE_EXTRACT_PERM; bsdtar->extract_flags |= ARCHIVE_EXTRACT_ACL; bsdtar->extract_flags |= ARCHIVE_EXTRACT_XATTR; bsdtar->extract_flags |= ARCHIVE_EXTRACT_FFLAGS; } +#ifdef _WIN32 + /* Windows cannot set UNIX like uid/gid. */ + bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_OWNER; +#endif bsdtar->argv = argv; bsdtar->argc = argc; /* * Comments following each option indicate where that option * originated: SUSv2, POSIX, GNU tar, star, etc. If there's * no such comment, then I don't know of anyone else who * implements that option. */ while ((opt = bsdtar_getopt(bsdtar)) != -1) { switch (opt) { case 'B': /* GNU tar */ /* libarchive doesn't need this; just ignore it. */ break; case 'b': /* SUSv2 */ t = atoi(bsdtar->optarg); if (t <= 0 || t > 1024) bsdtar_errc(bsdtar, 1, 0, "Argument to -b is out of range (1..1024)"); bsdtar->bytes_per_block = 512 * t; break; case 'C': /* GNU tar */ set_chdir(bsdtar, bsdtar->optarg); break; case 'c': /* SUSv2 */ set_mode(bsdtar, opt); break; case OPTION_CHECK_LINKS: /* GNU tar */ bsdtar->option_warn_links = 1; break; case OPTION_CHROOT: /* NetBSD */ bsdtar->option_chroot = 1; break; case OPTION_EXCLUDE: /* GNU tar */ if (exclude(bsdtar, bsdtar->optarg)) bsdtar_errc(bsdtar, 1, 0, "Couldn't exclude %s\n", bsdtar->optarg); break; case OPTION_FORMAT: /* GNU tar, others */ bsdtar->create_format = bsdtar->optarg; break; case 'f': /* SUSv2 */ bsdtar->filename = bsdtar->optarg; if (strcmp(bsdtar->filename, "-") == 0) bsdtar->filename = NULL; break; case 'H': /* BSD convention */ bsdtar->symlink_mode = 'H'; break; case 'h': /* Linux Standards Base, gtar; synonym for -L */ bsdtar->symlink_mode = 'L'; /* Hack: -h by itself is the "help" command. */ possible_help_request = 1; break; case OPTION_HELP: /* GNU tar, others */ long_help(bsdtar); exit(0); break; case 'I': /* GNU tar */ /* * TODO: Allow 'names' to come from an archive, * not just a text file. Design a good UI for * allowing names and mode/owner to be read * from an archive, with contents coming from * disk. This can be used to "refresh" an * archive or to design archives with special * permissions without having to create those * permissions on disk. */ bsdtar->names_from_file = bsdtar->optarg; break; case OPTION_INCLUDE: /* * Noone else has the @archive extension, so * noone else needs this to filter entries * when transforming archives. */ if (include(bsdtar, bsdtar->optarg)) bsdtar_errc(bsdtar, 1, 0, "Failed to add %s to inclusion list", bsdtar->optarg); break; case 'j': /* GNU tar */ #if HAVE_LIBBZ2 if (bsdtar->create_compression != '\0') bsdtar_errc(bsdtar, 1, 0, "Can't specify both -%c and -%c", opt, bsdtar->create_compression); bsdtar->create_compression = opt; #else bsdtar_warnc(bsdtar, 0, "bzip2 compression not supported by this version of bsdtar"); usage(bsdtar); #endif break; case 'k': /* GNU tar */ bsdtar->extract_flags |= ARCHIVE_EXTRACT_NO_OVERWRITE; break; case OPTION_KEEP_NEWER_FILES: /* GNU tar */ bsdtar->extract_flags |= ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER; break; case 'L': /* BSD convention */ bsdtar->symlink_mode = 'L'; break; case 'l': /* SUSv2 and GNU tar beginning with 1.16 */ /* GNU tar 1.13 used -l for --one-file-system */ bsdtar->option_warn_links = 1; break; case 'm': /* SUSv2 */ bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_TIME; break; case 'n': /* GNU tar */ bsdtar->option_no_subdirs = 1; break; /* * Selecting files by time: * --newer-?time='date' Only files newer than 'date' * --newer-?time-than='file' Only files newer than time * on specified file (useful for incremental backups) * TODO: Add corresponding "older" options to reverse these. */ case OPTION_NEWER_CTIME: /* GNU tar */ bsdtar->newer_ctime_sec = get_date(bsdtar->optarg); break; case OPTION_NEWER_CTIME_THAN: { struct stat st; if (stat(bsdtar->optarg, &st) != 0) bsdtar_errc(bsdtar, 1, 0, "Can't open file %s", bsdtar->optarg); bsdtar->newer_ctime_sec = st.st_ctime; bsdtar->newer_ctime_nsec = ARCHIVE_STAT_CTIME_NANOS(&st); } break; case OPTION_NEWER_MTIME: /* GNU tar */ bsdtar->newer_mtime_sec = get_date(bsdtar->optarg); break; case OPTION_NEWER_MTIME_THAN: { struct stat st; if (stat(bsdtar->optarg, &st) != 0) bsdtar_errc(bsdtar, 1, 0, "Can't open file %s", bsdtar->optarg); bsdtar->newer_mtime_sec = st.st_mtime; bsdtar->newer_mtime_nsec = ARCHIVE_STAT_MTIME_NANOS(&st); } break; case OPTION_NODUMP: /* star */ bsdtar->option_honor_nodump = 1; break; case OPTION_NO_SAME_OWNER: /* GNU tar */ bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_OWNER; break; case OPTION_NO_SAME_PERMISSIONS: /* GNU tar */ bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_PERM; bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_ACL; bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_XATTR; bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_FFLAGS; break; 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; case 'o': /* SUSv2 and GNU conflict here, but not fatally */ option_o = 1; /* Record it and resolve it later. */ break; case OPTION_ONE_FILE_SYSTEM: /* GNU tar */ bsdtar->option_dont_traverse_mounts = 1; break; #if 0 /* * The common BSD -P option is not necessary, since * our default is to archive symlinks, not follow * them. This is convenient, as -P conflicts with GNU * tar anyway. */ case 'P': /* BSD convention */ /* Default behavior, no option necessary. */ break; #endif case 'P': /* GNU tar */ bsdtar->extract_flags &= ~SECURITY; bsdtar->option_absolute_paths = 1; break; case 'p': /* GNU tar, star */ bsdtar->extract_flags |= ARCHIVE_EXTRACT_PERM; bsdtar->extract_flags |= ARCHIVE_EXTRACT_ACL; bsdtar->extract_flags |= ARCHIVE_EXTRACT_XATTR; bsdtar->extract_flags |= ARCHIVE_EXTRACT_FFLAGS; break; case OPTION_POSIX: /* GNU tar */ bsdtar->create_format = "pax"; break; case 'q': /* FreeBSD GNU tar --fast-read, NetBSD -q */ bsdtar->option_fast_read = 1; break; 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, 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(bsdtar->optarg); break; case 'T': /* GNU tar */ bsdtar->names_from_file = bsdtar->optarg; break; case 't': /* SUSv2 */ set_mode(bsdtar, opt); bsdtar->verbose++; break; case OPTION_TOTALS: /* GNU tar */ bsdtar->option_totals++; break; case 'U': /* GNU tar */ bsdtar->extract_flags |= ARCHIVE_EXTRACT_UNLINK; bsdtar->option_unlink_first = 1; break; case 'u': /* SUSv2 */ set_mode(bsdtar, opt); break; case 'v': /* SUSv2 */ bsdtar->verbose++; break; case OPTION_VERSION: /* GNU convention */ version(); break; #if 0 /* * The -W longopt feature is handled inside of * bsdtar_getopt(), so -W is not available here. */ case 'W': /* Obscure GNU convention. */ break; #endif case 'w': /* SUSv2 */ bsdtar->option_interactive = 1; break; case 'X': /* GNU tar */ if (exclude_from_file(bsdtar, bsdtar->optarg)) bsdtar_errc(bsdtar, 1, 0, "failed to process exclusions from file %s", bsdtar->optarg); break; case 'x': /* SUSv2 */ set_mode(bsdtar, opt); break; case 'y': /* FreeBSD version of GNU tar */ #if HAVE_LIBBZ2 if (bsdtar->create_compression != '\0') bsdtar_errc(bsdtar, 1, 0, "Can't specify both -%c and -%c", opt, bsdtar->create_compression); bsdtar->create_compression = opt; #else bsdtar_warnc(bsdtar, 0, "bzip2 compression not supported by this version of bsdtar"); usage(bsdtar); #endif break; case 'Z': /* GNU tar */ if (bsdtar->create_compression != '\0') bsdtar_errc(bsdtar, 1, 0, "Can't specify both -%c and -%c", opt, bsdtar->create_compression); bsdtar->create_compression = opt; break; case 'z': /* GNU tar, star, many others */ #if HAVE_LIBZ if (bsdtar->create_compression != '\0') bsdtar_errc(bsdtar, 1, 0, "Can't specify both -%c and -%c", opt, bsdtar->create_compression); bsdtar->create_compression = opt; #else bsdtar_warnc(bsdtar, 0, "gzip compression not supported by this version of bsdtar"); usage(bsdtar); #endif break; case OPTION_USE_COMPRESS_PROGRAM: bsdtar->compress_program = bsdtar->optarg; break; default: usage(bsdtar); } } /* * Sanity-check options. */ /* If no "real" mode was specified, treat -h as --help. */ if ((bsdtar->mode == '\0') && possible_help_request) { long_help(bsdtar); exit(0); } /* Otherwise, a mode is required. */ if (bsdtar->mode == '\0') bsdtar_errc(bsdtar, 1, 0, "Must specify one of -c, -r, -t, -u, -x"); /* Check boolean options only permitted in certain modes. */ if (bsdtar->option_dont_traverse_mounts) only_mode(bsdtar, "--one-file-system", "cru"); if (bsdtar->option_fast_read) only_mode(bsdtar, "--fast-read", "xt"); if (bsdtar->option_honor_nodump) only_mode(bsdtar, "--nodump", "cru"); if (option_o > 0) { switch (bsdtar->mode) { case 'c': /* * In GNU tar, -o means "old format." The * "ustar" format is the closest thing * supported by libarchive. */ bsdtar->create_format = "ustar"; /* TODO: bsdtar->create_format = "v7"; */ break; case 'x': /* POSIX-compatible behavior. */ bsdtar->option_no_owner = 1; bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_OWNER; break; default: only_mode(bsdtar, "-o", "xc"); break; } } if (bsdtar->option_no_subdirs) only_mode(bsdtar, "-n", "cru"); if (bsdtar->option_stdout) only_mode(bsdtar, "-O", "xt"); if (bsdtar->option_unlink_first) only_mode(bsdtar, "-U", "x"); if (bsdtar->option_warn_links) only_mode(bsdtar, "--check-links", "cr"); /* Check other parameters only permitted in certain modes. */ if (bsdtar->create_compression != '\0') { strcpy(buff, "-?"); buff[1] = bsdtar->create_compression; only_mode(bsdtar, buff, "cxt"); } if (bsdtar->create_format != NULL) only_mode(bsdtar, "--format", "cru"); if (bsdtar->symlink_mode != '\0') { strcpy(buff, "-?"); buff[1] = bsdtar->symlink_mode; only_mode(bsdtar, buff, "cru"); } if (bsdtar->strip_components != 0) only_mode(bsdtar, "--strip-components", "xt"); switch(bsdtar->mode) { case 'c': tar_mode_c(bsdtar); break; case 'r': tar_mode_r(bsdtar); break; case 't': tar_mode_t(bsdtar); break; case 'u': tar_mode_u(bsdtar); break; case 'x': tar_mode_x(bsdtar); break; } 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."); return (bsdtar->return_value); } static void set_mode(struct bsdtar *bsdtar, char opt) { if (bsdtar->mode != '\0' && bsdtar->mode != opt) bsdtar_errc(bsdtar, 1, 0, "Can't specify both -%c and -%c", opt, bsdtar->mode); bsdtar->mode = opt; } /* * Verify that the mode is correct. */ static void only_mode(struct bsdtar *bsdtar, const char *opt, const char *valid_modes) { if (strchr(valid_modes, bsdtar->mode) == NULL) bsdtar_errc(bsdtar, 1, 0, "Option %s is not permitted in mode -%c", opt, bsdtar->mode); } void usage(struct bsdtar *bsdtar) { const char *p; p = bsdtar->progname; fprintf(stderr, "Usage:\n"); fprintf(stderr, " List: %s -tf \n", p); fprintf(stderr, " Extract: %s -xf \n", p); fprintf(stderr, " Create: %s -cf [filenames...]\n", p); fprintf(stderr, " Help: %s --help\n", p); exit(1); } static void version(void) { printf("bsdtar %s - %s\n", BSDTAR_VERSION_STRING, archive_version()); exit(0); } static const char *long_help_msg = "First option must be a mode specifier:\n" " -c Create -r Add/Replace -t List -u Update -x Extract\n" "Common Options:\n" " -b # Use # 512-byte records per I/O block\n" " -f Location of archive (default " _PATH_DEFTAPE ")\n" " -v Verbose\n" " -w Interactive\n" "Create: %p -c [options] [ | | @ | -C ]\n" " , add these items to archive\n" " -z, -j Compress archive with gzip/bzip2\n" " --format {ustar|pax|cpio|shar} Select archive format\n" " --exclude Skip files that match pattern\n" " -C Change to before processing remaining files\n" " @ Add entries from to output\n" "List: %p -t [options] []\n" " If specified, list only entries that match\n" "Extract: %p -x [options] []\n" " If specified, extract only entries that match\n" " -k Keep (don't overwrite) existing files\n" " -m Don't restore modification times\n" " -O Write entries to stdout, don't restore to disk\n" " -p Restore permissions (including ACLs, owner, file flags)\n"; /* * Note that the word 'bsdtar' will always appear in the first line * of output. * * In particular, /bin/sh scripts that need to test for the presence * of bsdtar can use the following template: * * if (tar --help 2>&1 | grep bsdtar >/dev/null 2>&1 ) then \ * echo bsdtar; else echo not bsdtar; fi */ static void long_help(struct bsdtar *bsdtar) { const char *prog; const char *p; prog = bsdtar->progname; fflush(stderr); p = (strcmp(prog,"bsdtar") != 0) ? "(bsdtar)" : ""; printf("%s%s: manipulate archive files\n", prog, p); for (p = long_help_msg; *p != '\0'; p++) { if (*p == '%') { if (p[1] == 'p') { fputs(prog, stdout); p++; } else putchar('%'); } else putchar(*p); } version(); } diff --git a/usr.bin/tar/bsdtar_platform.h b/usr.bin/tar/bsdtar_platform.h index a641f5b72e2c..828eb38fa67b 100644 --- a/usr.bin/tar/bsdtar_platform.h +++ b/usr.bin/tar/bsdtar_platform.h @@ -1,167 +1,173 @@ /*- * 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. * * $FreeBSD$ */ /* * This header is the first thing included in any of the bsdtar * source files. As far as possible, platform-specific issues should * be dealt with here and not within individual source files. */ #ifndef BSDTAR_PLATFORM_H_INCLUDED #define BSDTAR_PLATFORM_H_INCLUDED #if defined(PLATFORM_CONFIG_H) /* Use hand-built config.h in environments that need it. */ #include PLATFORM_CONFIG_H #elif defined(HAVE_CONFIG_H) /* Most POSIX platforms use the 'configure' script to build config.h */ #include "config.h" #else /* Warn if bsdtar hasn't been (automatically or manually) configured. */ #error Oops: No config.h and no built-in configuration in bsdtar_platform.h. #endif /* !HAVE_CONFIG_H */ /* No non-FreeBSD platform will have __FBSDID, so just define it here. */ #ifdef __FreeBSD__ #include /* For __FBSDID */ #else /* Just leaving this macro replacement empty leads to a dangling semicolon. */ #define __FBSDID(a) struct _undefined_hack #endif #ifdef HAVE_LIBARCHIVE /* If we're using the platform libarchive, include system headers. */ #include #include #else /* Otherwise, include user headers. */ #include "archive.h" #include "archive_entry.h" #endif /* * Does this platform have complete-looking POSIX-style ACL support, * including some variant of the acl_get_perm() function (which was * omitted from the POSIX.1e draft)? */ #if HAVE_SYS_ACL_H && HAVE_ACL_PERMSET_T && HAVE_ACL_USER #if HAVE_ACL_GET_PERM || HAVE_ACL_GET_PERM_NP #define HAVE_POSIX_ACL 1 #endif #endif #ifdef HAVE_LIBACL #include #endif #if HAVE_ACL_GET_PERM #define ACL_GET_PERM acl_get_perm #else #if HAVE_ACL_GET_PERM_NP #define ACL_GET_PERM acl_get_perm_np #endif #endif /* * Include "dirent.h" (or it's equivalent on several different platforms). * * This is slightly modified from the GNU autoconf recipe. * In particular, FreeBSD includes d_namlen in it's dirent structure, * so my configure script includes an explicit test for the d_namlen * field. */ #if HAVE_DIRENT_H # include # if HAVE_DIRENT_D_NAMLEN # define DIRENT_NAMLEN(dirent) (dirent)->d_namlen # else # define DIRENT_NAMLEN(dirent) strlen((dirent)->d_name) # endif #else # define dirent direct # define DIRENT_NAMLEN(dirent) (dirent)->d_namlen # if HAVE_SYS_NDIR_H # include # endif # if HAVE_SYS_DIR_H # include # endif # if HAVE_NDIR_H # include # endif #endif /* * We need to be able to display a filesize using printf(). The type * and format string here must be compatible with one another and * large enough for any file. */ #if HAVE_UINTMAX_T #define BSDTAR_FILESIZE_TYPE uintmax_t #define BSDTAR_FILESIZE_PRINTF "%ju" #else #if HAVE_UNSIGNED_LONG_LONG #define BSDTAR_FILESIZE_TYPE unsigned long long #define BSDTAR_FILESIZE_PRINTF "%llu" #else #define BSDTAR_FILESIZE_TYPE unsigned long #define BSDTAR_FILESIZE_PRINTF "%lu" #endif #endif #if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC #define ARCHIVE_STAT_CTIME_NANOS(st) (st)->st_ctimespec.tv_nsec #define ARCHIVE_STAT_MTIME_NANOS(st) (st)->st_mtimespec.tv_nsec #elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC #define ARCHIVE_STAT_CTIME_NANOS(st) (st)->st_ctim.tv_nsec #define ARCHIVE_STAT_MTIME_NANOS(st) (st)->st_mtim.tv_nsec #elif HAVE_STRUCT_STAT_ST_MTIME_N #define ARCHIVE_STAT_CTIME_NANOS(st) (st)->st_ctime_n #define ARCHIVE_STAT_MTIME_NANOS(st) (st)->st_mtime_n #elif HAVE_STRUCT_STAT_ST_UMTIME #define ARCHIVE_STAT_CTIME_NANOS(st) (st)->st_uctime * 1000 #define ARCHIVE_STAT_MTIME_NANOS(st) (st)->st_umtime * 1000 #elif HAVE_STRUCT_STAT_ST_MTIME_USEC #define ARCHIVE_STAT_CTIME_NANOS(st) (st)->st_ctime_usec * 1000 #define ARCHIVE_STAT_MTIME_NANOS(st) (st)->st_mtime_usec * 1000 #else #define ARCHIVE_STAT_CTIME_NANOS(st) (0) #define ARCHIVE_STAT_MTIME_NANOS(st) (0) #endif /* How to mark functions that don't return. */ /* This facilitates use of some newer static code analysis tools. */ #undef __LA_DEAD #if defined(__GNUC__) && (__GNUC__ > 2 || \ (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)) #define __LA_DEAD __attribute__((__noreturn__)) #else #define __LA_DEAD #endif +#ifdef _WIN32 +#include "bsdtar_windows.h" +#else +#define bsdtar_is_privileged(bsdtar) (bsdtar->user_uid == 0) +#endif + #endif /* !BSDTAR_PLATFORM_H_INCLUDED */ diff --git a/usr.bin/tar/getdate.y b/usr.bin/tar/getdate.y index 16162c35424b..28b402ef179f 100644 --- a/usr.bin/tar/getdate.y +++ b/usr.bin/tar/getdate.y @@ -1,811 +1,815 @@ %{ /* * March 2005: Further modified and simplified by Tim Kientzle: * Eliminate minutes-based calculations (just do everything in * seconds), have lexer only recognize unsigned integers (handle '+' * and '-' characters in grammar), combine tables into one table with * explicit abbreviation notes, do am/pm adjustments in the grammar * (eliminate some state variables and post-processing). Among other * things, these changes eliminated two shift/reduce conflicts. (Went * from 10 to 8.) * All of Tim Kientzle's changes to this file are public domain. */ /* ** Originally written by Steven M. Bellovin while ** at the University of North Carolina at Chapel Hill. Later tweaked by ** a couple of people on Usenet. Completely overhauled by Rich $alz ** and Jim Berets in August, 1990; ** ** This grammar has 10 shift/reduce conflicts. ** ** This code is in the public domain and has no copyright. */ /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */ /* SUPPRESS 288 on yyerrlab *//* Label unused */ #ifdef __FreeBSD__ #include __FBSDID("$FreeBSD$"); #endif #include #include #include #include #include +#ifdef _MSC_VER +#define __STDC__ /* for a bug of bison 2.1 on Windows */ +#endif + #define yyparse getdate_yyparse #define yylex getdate_yylex #define yyerror getdate_yyerror static int yyparse(void); static int yylex(void); static int yyerror(const char *); time_t get_date(char *); #define EPOCH 1970 #define HOUR(x) ((time_t)(x) * 60) #define SECSPERDAY (24L * 60L * 60L) /* ** Daylight-savings mode: on, off, or not yet known. */ typedef enum _DSTMODE { DSTon, DSToff, DSTmaybe } DSTMODE; /* ** Meridian: am or pm. */ enum { tAM, tPM }; /* ** Global variables. We could get rid of most of these by using a good ** union as the yacc stack. (This routine was originally written before ** yacc had the %union construct.) Maybe someday; right now we only use ** the %union very rarely. */ static char *yyInput; static DSTMODE yyDSTmode; static time_t yyDayOrdinal; static time_t yyDayNumber; static int yyHaveDate; static int yyHaveDay; static int yyHaveRel; static int yyHaveTime; static int yyHaveZone; static time_t yyTimezone; static time_t yyDay; static time_t yyHour; static time_t yyMinutes; static time_t yyMonth; static time_t yySeconds; static time_t yyYear; static time_t yyRelMonth; static time_t yyRelSeconds; %} %union { time_t Number; } %token tAGO tDAY tDAYZONE tAMPM tMONTH tMONTH_UNIT tSEC_UNIT tUNUMBER %token tZONE tDST %type tDAY tDAYZONE tMONTH tMONTH_UNIT %type tSEC_UNIT tUNUMBER tZONE tAMPM %% spec : /* NULL */ | spec item ; item : time { yyHaveTime++; } | zone { yyHaveZone++; } | date { yyHaveDate++; } | day { yyHaveDay++; } | rel { yyHaveRel++; } | number ; time : tUNUMBER tAMPM { /* "7am" */ yyHour = $1; if (yyHour == 12) yyHour = 0; yyMinutes = 0; yySeconds = 0; if ($2 == tPM) yyHour += 12; } | bare_time { /* "7:12:18" "19:17" */ } | bare_time tAMPM { /* "7:12pm", "12:20:13am" */ if (yyHour == 12) yyHour = 0; if ($2 == tPM) yyHour += 12; } | bare_time '+' tUNUMBER { /* "7:14+0700" */ yyDSTmode = DSToff; yyTimezone = - ($3 % 100 + ($3 / 100) * 60); } | bare_time '-' tUNUMBER { /* "19:14:12-0530" */ yyDSTmode = DSToff; yyTimezone = + ($3 % 100 + ($3 / 100) * 60); } ; bare_time : tUNUMBER ':' tUNUMBER { yyHour = $1; yyMinutes = $3; yySeconds = 0; } | tUNUMBER ':' tUNUMBER ':' tUNUMBER { yyHour = $1; yyMinutes = $3; yySeconds = $5; } ; zone : tZONE { yyTimezone = $1; yyDSTmode = DSToff; } | tDAYZONE { yyTimezone = $1; yyDSTmode = DSTon; } | tZONE tDST { yyTimezone = $1; yyDSTmode = DSTon; } ; day : tDAY { yyDayOrdinal = 1; yyDayNumber = $1; } | tDAY ',' { /* "tue," "wednesday," */ yyDayOrdinal = 1; yyDayNumber = $1; } | tUNUMBER tDAY { /* "second tues" "3 wed" */ yyDayOrdinal = $1; yyDayNumber = $2; } ; date : tUNUMBER '/' tUNUMBER { /* "1/15" */ yyMonth = $1; yyDay = $3; } | tUNUMBER '/' tUNUMBER '/' tUNUMBER { if ($1 >= 13) { /* First number is big: 2004/01/29, 99/02/17 */ yyYear = $1; yyMonth = $3; yyDay = $5; } else if (($5 >= 13) || ($3 >= 13)) { /* Last number is big: 01/07/98 */ /* Middle number is big: 01/29/04 */ yyMonth = $1; yyDay = $3; yyYear = $5; } else { /* No significant clues: 02/03/04 */ yyMonth = $1; yyDay = $3; yyYear = $5; } } | tUNUMBER '-' tUNUMBER '-' tUNUMBER { /* ISO 8601 format. yyyy-mm-dd. */ yyYear = $1; yyMonth = $3; yyDay = $5; } | tUNUMBER '-' tMONTH '-' tUNUMBER { if ($1 > 31) { /* e.g. 1992-Jun-17 */ yyYear = $1; yyMonth = $3; yyDay = $5; } else { /* e.g. 17-JUN-1992. */ yyDay = $1; yyMonth = $3; yyYear = $5; } } | tMONTH tUNUMBER { /* "May 3" */ yyMonth = $1; yyDay = $2; } | tMONTH tUNUMBER ',' tUNUMBER { /* "June 17, 2001" */ yyMonth = $1; yyDay = $2; yyYear = $4; } | tUNUMBER tMONTH { /* "12 Sept" */ yyDay = $1; yyMonth = $2; } | tUNUMBER tMONTH tUNUMBER { /* "12 Sept 1997" */ yyDay = $1; yyMonth = $2; yyYear = $3; } ; rel : relunit tAGO { yyRelSeconds = -yyRelSeconds; yyRelMonth = -yyRelMonth; } | relunit ; relunit : '-' tUNUMBER tSEC_UNIT { /* "-3 hours" */ yyRelSeconds -= $2 * $3; } | '+' tUNUMBER tSEC_UNIT { /* "+1 minute" */ yyRelSeconds += $2 * $3; } | tUNUMBER tSEC_UNIT { /* "1 day" */ yyRelSeconds += $1 * $2; } | tSEC_UNIT { /* "hour" */ yyRelSeconds += $1; } | '-' tUNUMBER tMONTH_UNIT { /* "-3 months" */ yyRelMonth -= $2 * $3; } | '+' tUNUMBER tMONTH_UNIT { /* "+5 years" */ yyRelMonth += $2 * $3; } | tUNUMBER tMONTH_UNIT { /* "2 years" */ yyRelMonth += $1 * $2; } | tMONTH_UNIT { /* "6 months" */ yyRelMonth += $1; } ; number : tUNUMBER { if (yyHaveTime && yyHaveDate && !yyHaveRel) yyYear = $1; else { if($1>10000) { /* "20040301" */ yyHaveDate++; yyDay= ($1)%100; yyMonth= ($1/100)%100; yyYear = $1/10000; } else { /* "513" is same as "5:13" */ yyHaveTime++; if ($1 < 100) { yyHour = $1; yyMinutes = 0; } else { yyHour = $1 / 100; yyMinutes = $1 % 100; } yySeconds = 0; } } } ; %% static struct TABLE { size_t abbrev; const char *name; int type; time_t value; } const TimeWords[] = { /* am/pm */ { 0, "am", tAMPM, tAM }, { 0, "pm", tAMPM, tPM }, /* Month names. */ { 3, "january", tMONTH, 1 }, { 3, "february", tMONTH, 2 }, { 3, "march", tMONTH, 3 }, { 3, "april", tMONTH, 4 }, { 3, "may", tMONTH, 5 }, { 3, "june", tMONTH, 6 }, { 3, "july", tMONTH, 7 }, { 3, "august", tMONTH, 8 }, { 3, "september", tMONTH, 9 }, { 3, "october", tMONTH, 10 }, { 3, "november", tMONTH, 11 }, { 3, "december", tMONTH, 12 }, /* Days of the week. */ { 2, "sunday", tDAY, 0 }, { 3, "monday", tDAY, 1 }, { 2, "tuesday", tDAY, 2 }, { 3, "wednesday", tDAY, 3 }, { 2, "thursday", tDAY, 4 }, { 2, "friday", tDAY, 5 }, { 2, "saturday", tDAY, 6 }, /* Timezones: Offsets are in minutes. */ { 0, "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */ { 0, "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */ { 0, "utc", tZONE, HOUR( 0) }, { 0, "wet", tZONE, HOUR( 0) }, /* Western European */ { 0, "bst", tDAYZONE, HOUR( 0) }, /* British Summer */ { 0, "wat", tZONE, HOUR( 1) }, /* West Africa */ { 0, "at", tZONE, HOUR( 2) }, /* Azores */ /* { 0, "bst", tZONE, HOUR( 3) }, */ /* Brazil Standard: Conflict */ /* { 0, "gst", tZONE, HOUR( 3) }, */ /* Greenland Standard: Conflict*/ { 0, "nft", tZONE, HOUR(3)+30 }, /* Newfoundland */ { 0, "nst", tZONE, HOUR(3)+30 }, /* Newfoundland Standard */ { 0, "ndt", tDAYZONE, HOUR(3)+30 }, /* Newfoundland Daylight */ { 0, "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */ { 0, "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */ { 0, "est", tZONE, HOUR( 5) }, /* Eastern Standard */ { 0, "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */ { 0, "cst", tZONE, HOUR( 6) }, /* Central Standard */ { 0, "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */ { 0, "mst", tZONE, HOUR( 7) }, /* Mountain Standard */ { 0, "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ { 0, "pst", tZONE, HOUR( 8) }, /* Pacific Standard */ { 0, "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ { 0, "yst", tZONE, HOUR( 9) }, /* Yukon Standard */ { 0, "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ { 0, "hst", tZONE, HOUR(10) }, /* Hawaii Standard */ { 0, "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ { 0, "cat", tZONE, HOUR(10) }, /* Central Alaska */ { 0, "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */ { 0, "nt", tZONE, HOUR(11) }, /* Nome */ { 0, "idlw", tZONE, HOUR(12) }, /* Intl Date Line West */ { 0, "cet", tZONE, -HOUR(1) }, /* Central European */ { 0, "met", tZONE, -HOUR(1) }, /* Middle European */ { 0, "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */ { 0, "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */ { 0, "swt", tZONE, -HOUR(1) }, /* Swedish Winter */ { 0, "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */ { 0, "fwt", tZONE, -HOUR(1) }, /* French Winter */ { 0, "fst", tDAYZONE, -HOUR(1) }, /* French Summer */ { 0, "eet", tZONE, -HOUR(2) }, /* Eastern Eur, USSR Zone 1 */ { 0, "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */ { 0, "it", tZONE, -HOUR(3)-30 },/* Iran */ { 0, "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */ { 0, "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */ { 0, "ist", tZONE, -HOUR(5)-30 },/* Indian Standard */ { 0, "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */ /* { 0, "nst", tZONE, -HOUR(6.5) }, */ /* North Sumatra: Conflict */ /* { 0, "sst", tZONE, -HOUR(7) }, */ /* So Sumatra, USSR 6: Conflict */ { 0, "wast", tZONE, -HOUR(7) }, /* West Australian Standard */ { 0, "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */ { 0, "jt", tZONE, -HOUR(7)-30 },/* Java (3pm in Cronusland!)*/ { 0, "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */ { 0, "jst", tZONE, -HOUR(9) }, /* Japan Std, USSR Zone 8 */ { 0, "cast", tZONE, -HOUR(9)-30 },/* Central Australian Std */ { 0, "cadt", tDAYZONE, -HOUR(9)-30 },/* Central Australian Daylt */ { 0, "east", tZONE, -HOUR(10) }, /* Eastern Australian Std */ { 0, "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylt */ { 0, "gst", tZONE, -HOUR(10) }, /* Guam Std, USSR Zone 9 */ { 0, "nzt", tZONE, -HOUR(12) }, /* New Zealand */ { 0, "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */ { 0, "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */ { 0, "idle", tZONE, -HOUR(12) }, /* Intl Date Line East */ { 0, "dst", tDST, 0 }, /* Time units. */ { 4, "years", tMONTH_UNIT, 12 }, { 5, "months", tMONTH_UNIT, 1 }, { 9, "fortnights", tSEC_UNIT, 14 * 24 * 60 * 60 }, { 4, "weeks", tSEC_UNIT, 7 * 24 * 60 * 60 }, { 3, "days", tSEC_UNIT, 1 * 24 * 60 * 60 }, { 4, "hours", tSEC_UNIT, 60 * 60 }, { 3, "minutes", tSEC_UNIT, 60 }, { 3, "seconds", tSEC_UNIT, 1 }, /* Relative-time words. */ { 0, "tomorrow", tSEC_UNIT, 1 * 24 * 60 * 60 }, { 0, "yesterday", tSEC_UNIT, -1 * 24 * 60 * 60 }, { 0, "today", tSEC_UNIT, 0 }, { 0, "now", tSEC_UNIT, 0 }, { 0, "last", tUNUMBER, -1 }, { 0, "this", tSEC_UNIT, 0 }, { 0, "next", tUNUMBER, 2 }, { 0, "first", tUNUMBER, 1 }, { 0, "1st", tUNUMBER, 1 }, /* { 0, "second", tUNUMBER, 2 }, */ { 0, "2nd", tUNUMBER, 2 }, { 0, "third", tUNUMBER, 3 }, { 0, "3rd", tUNUMBER, 3 }, { 0, "fourth", tUNUMBER, 4 }, { 0, "4th", tUNUMBER, 4 }, { 0, "fifth", tUNUMBER, 5 }, { 0, "5th", tUNUMBER, 5 }, { 0, "sixth", tUNUMBER, 6 }, { 0, "seventh", tUNUMBER, 7 }, { 0, "eighth", tUNUMBER, 8 }, { 0, "ninth", tUNUMBER, 9 }, { 0, "tenth", tUNUMBER, 10 }, { 0, "eleventh", tUNUMBER, 11 }, { 0, "twelfth", tUNUMBER, 12 }, { 0, "ago", tAGO, 1 }, /* Military timezones. */ { 0, "a", tZONE, HOUR( 1) }, { 0, "b", tZONE, HOUR( 2) }, { 0, "c", tZONE, HOUR( 3) }, { 0, "d", tZONE, HOUR( 4) }, { 0, "e", tZONE, HOUR( 5) }, { 0, "f", tZONE, HOUR( 6) }, { 0, "g", tZONE, HOUR( 7) }, { 0, "h", tZONE, HOUR( 8) }, { 0, "i", tZONE, HOUR( 9) }, { 0, "k", tZONE, HOUR( 10) }, { 0, "l", tZONE, HOUR( 11) }, { 0, "m", tZONE, HOUR( 12) }, { 0, "n", tZONE, HOUR(- 1) }, { 0, "o", tZONE, HOUR(- 2) }, { 0, "p", tZONE, HOUR(- 3) }, { 0, "q", tZONE, HOUR(- 4) }, { 0, "r", tZONE, HOUR(- 5) }, { 0, "s", tZONE, HOUR(- 6) }, { 0, "t", tZONE, HOUR(- 7) }, { 0, "u", tZONE, HOUR(- 8) }, { 0, "v", tZONE, HOUR(- 9) }, { 0, "w", tZONE, HOUR(-10) }, { 0, "x", tZONE, HOUR(-11) }, { 0, "y", tZONE, HOUR(-12) }, { 0, "z", tZONE, HOUR( 0) }, /* End of table. */ { 0, NULL, 0, 0 } }; /* ARGSUSED */ static int yyerror(const char *s) { (void)s; return 0; } static time_t ToSeconds(time_t Hours, time_t Minutes, time_t Seconds) { if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) return -1; if (Hours < 0 || Hours > 23) return -1; return (Hours * 60L + Minutes) * 60L + Seconds; } /* Year is either * A number from 0 to 99, which means a year from 1970 to 2069, or * The actual year (>=100). */ static time_t Convert(time_t Month, time_t Day, time_t Year, time_t Hours, time_t Minutes, time_t Seconds, DSTMODE DSTmode) { static int DaysInMonth[12] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; time_t tod; time_t Julian; int i; if (Year < 69) Year += 2000; else if (Year < 100) Year += 1900; DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0) ? 29 : 28; /* Checking for 2038 bogusly assumes that time_t is 32 bits. But I'm too lazy to try to check for time_t overflow in another way. */ if (Year < EPOCH || Year > 2038 || Month < 1 || Month > 12 /* Lint fluff: "conversion from long may lose accuracy" */ || Day < 1 || Day > DaysInMonth[(int)--Month]) return -1; Julian = Day - 1; for (i = 0; i < Month; i++) Julian += DaysInMonth[i]; for (i = EPOCH; i < Year; i++) Julian += 365 + (i % 4 == 0); Julian *= SECSPERDAY; Julian += yyTimezone * 60L; if ((tod = ToSeconds(Hours, Minutes, Seconds)) < 0) return -1; Julian += tod; if (DSTmode == DSTon || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst)) Julian -= 60 * 60; return Julian; } static time_t DSTcorrect(time_t Start, time_t Future) { time_t StartDay; time_t FutureDay; StartDay = (localtime(&Start)->tm_hour + 1) % 24; FutureDay = (localtime(&Future)->tm_hour + 1) % 24; return (Future - Start) + (StartDay - FutureDay) * 60L * 60L; } static time_t RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber) { struct tm *tm; time_t now; now = Start; tm = localtime(&now); now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7); now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1); return DSTcorrect(Start, now); } static time_t RelativeMonth(time_t Start, time_t RelMonth) { struct tm *tm; time_t Month; time_t Year; if (RelMonth == 0) return 0; tm = localtime(&Start); Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth; Year = Month / 12; Month = Month % 12 + 1; return DSTcorrect(Start, Convert(Month, (time_t)tm->tm_mday, Year, (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec, DSTmaybe)); } static int yylex(void) { char c; char buff[64]; for ( ; ; ) { while (isspace((unsigned char)*yyInput)) yyInput++; /* Skip parenthesized comments. */ if (*yyInput == '(') { int Count = 0; do { c = *yyInput++; if (c == '\0') return c; if (c == '(') Count++; else if (c == ')') Count--; } while (Count > 0); continue; } /* Try the next token in the word table first. */ /* This allows us to match "2nd", for example. */ { char *src = yyInput; const struct TABLE *tp; unsigned i = 0; /* Force to lowercase and strip '.' characters. */ while (*src != '\0' && (isalnum((unsigned char)*src) || *src == '.') && i < sizeof(buff)-1) { if (*src != '.') { if (isupper((unsigned char)*src)) buff[i++] = tolower((unsigned char)*src); else buff[i++] = *src; } src++; } buff[i++] = '\0'; /* * Find the first match. If the word can be * abbreviated, make sure we match at least * the minimum abbreviation. */ for (tp = TimeWords; tp->name; tp++) { size_t abbrev = tp->abbrev; if (abbrev == 0) abbrev = strlen(tp->name); if (strlen(buff) >= abbrev && strncmp(tp->name, buff, strlen(buff)) == 0) { /* Skip over token. */ yyInput = src; /* Return the match. */ yylval.Number = tp->value; return tp->type; } } } /* * Not in the word table, maybe it's a number. Note: * Because '-' and '+' have other special meanings, I * don't deal with signed numbers here. */ if (isdigit((unsigned char)(c = *yyInput))) { for (yylval.Number = 0; isdigit((unsigned char)(c = *yyInput++)); ) yylval.Number = 10 * yylval.Number + c - '0'; yyInput--; return (tUNUMBER); } return (*yyInput++); } } #define TM_YEAR_ORIGIN 1900 /* Yield A - B, measured in seconds. */ static long difftm (struct tm *a, struct tm *b) { int ay = a->tm_year + (TM_YEAR_ORIGIN - 1); int by = b->tm_year + (TM_YEAR_ORIGIN - 1); int days = ( /* difference in day of year */ a->tm_yday - b->tm_yday /* + intervening leap days */ + ((ay >> 2) - (by >> 2)) - (ay/100 - by/100) + ((ay/100 >> 2) - (by/100 >> 2)) /* + difference in years * 365 */ + (long)(ay-by) * 365 ); return (60*(60*(24*days + (a->tm_hour - b->tm_hour)) + (a->tm_min - b->tm_min)) + (a->tm_sec - b->tm_sec)); } time_t get_date(char *p) { struct tm *tm; struct tm gmt, *gmt_ptr; time_t Start; time_t tod; time_t nowtime; long tzone; memset(&gmt, 0, sizeof(gmt)); yyInput = p; (void)time (&nowtime); gmt_ptr = gmtime (&nowtime); if (gmt_ptr != NULL) { /* Copy, in case localtime and gmtime use the same buffer. */ gmt = *gmt_ptr; } if (! (tm = localtime (&nowtime))) return -1; if (gmt_ptr != NULL) tzone = difftm (&gmt, tm) / 60; else /* This system doesn't understand timezones; fake it. */ tzone = 0; if(tm->tm_isdst) tzone += 60; yyYear = tm->tm_year + 1900; yyMonth = tm->tm_mon + 1; yyDay = tm->tm_mday; yyTimezone = tzone; yyDSTmode = DSTmaybe; yyHour = 0; yyMinutes = 0; yySeconds = 0; yyRelSeconds = 0; yyRelMonth = 0; yyHaveDate = 0; yyHaveDay = 0; yyHaveRel = 0; yyHaveTime = 0; yyHaveZone = 0; if (yyparse() || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1) return -1; if (yyHaveDate || yyHaveTime || yyHaveDay) { Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds, yyDSTmode); if (Start < 0) return -1; } else { Start = nowtime; if (!yyHaveRel) Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec; } Start += yyRelSeconds; Start += RelativeMonth(Start, yyRelMonth); if (yyHaveDay && !yyHaveDate) { tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber); Start += tod; } /* Have to do *something* with a legitimate -1 so it's * distinguishable from the error return value. (Alternately * could set errno on error.) */ return Start == -1 ? 0 : Start; } #if defined(TEST) /* ARGSUSED */ int main(int argc, char **argv) { time_t d; while (*++argv != NULL) { (void)printf("Input: %s\n", *argv); d = get_date(*argv); if (d == -1) (void)printf("Bad format - couldn't convert.\n"); else (void)printf("Output: %s\n", ctime(&d)); } exit(0); /* NOTREACHED */ } #endif /* defined(TEST) */ diff --git a/usr.bin/tar/read.c b/usr.bin/tar/read.c index 35cea89a95a2..3aa164feb608 100644 --- a/usr.bin/tar/read.c +++ b/usr.bin/tar/read.c @@ -1,401 +1,409 @@ /*- * 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 "bsdtar_platform.h" __FBSDID("$FreeBSD$"); #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef MAJOR_IN_MKDEV #include #elif defined(MAJOR_IN_SYSMACROS) #include #endif #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_GRP_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #ifdef HAVE_PWD_H #include #endif #include #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_TIME_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #include "bsdtar.h" static void list_item_verbose(struct bsdtar *, FILE *, struct archive_entry *); static void read_archive(struct bsdtar *bsdtar, char mode); void tar_mode_t(struct bsdtar *bsdtar) { read_archive(bsdtar, 't'); unmatched_inclusions_warn(bsdtar, "Not found in archive"); } void tar_mode_x(struct bsdtar *bsdtar) { /* We want to catch SIGINFO and SIGUSR1. */ siginfo_init(bsdtar); read_archive(bsdtar, 'x'); unmatched_inclusions_warn(bsdtar, "Not found in archive"); /* Restore old SIGINFO + SIGUSR1 handlers. */ siginfo_done(bsdtar); } static void progress_func(void * cookie) { struct bsdtar * bsdtar = cookie; siginfo_printinfo(bsdtar, 0); } /* * Handle 'x' and 't' modes. */ static void read_archive(struct bsdtar *bsdtar, char mode) { FILE *out; struct archive *a; struct archive_entry *entry; const struct stat *st; int r; while (*bsdtar->argv) { include(bsdtar, *bsdtar->argv); bsdtar->argv++; } if (bsdtar->names_from_file != NULL) include_from_file(bsdtar, bsdtar->names_from_file); a = archive_read_new(); if (bsdtar->compress_program != NULL) archive_read_support_compression_program(a, bsdtar->compress_program); else archive_read_support_compression_all(a); archive_read_support_format_all(a); if (archive_read_open_file(a, bsdtar->filename, bsdtar->bytes_per_block != 0 ? bsdtar->bytes_per_block : DEFAULT_BYTES_PER_BLOCK)) bsdtar_errc(bsdtar, 1, 0, "Error opening archive: %s", archive_error_string(a)); do_chdir(bsdtar); if (mode == 'x') { /* Set an extract callback so that we can handle SIGINFO. */ archive_read_extract_set_progress_callback(a, progress_func, bsdtar); } if (mode == 'x' && bsdtar->option_chroot) { #if HAVE_CHROOT if (chroot(".") != 0) bsdtar_errc(bsdtar, 1, errno, "Can't chroot to \".\""); #else bsdtar_errc(bsdtar, 1, 0, "chroot isn't supported on this platform"); #endif } for (;;) { /* Support --fast-read option */ if (bsdtar->option_fast_read && unmatched_inclusions(bsdtar) == 0) break; r = archive_read_next_header(a, &entry); if (r == ARCHIVE_EOF) break; if (r < ARCHIVE_OK) bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); if (r <= ARCHIVE_WARN) bsdtar->return_value = 1; if (r == ARCHIVE_RETRY) { /* Retryable error: try again */ bsdtar_warnc(bsdtar, 0, "Retrying..."); continue; } 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. */ st = archive_entry_stat(entry); if (bsdtar->newer_ctime_sec > 0) { if (st->st_ctime < bsdtar->newer_ctime_sec) continue; /* Too old, skip it. */ if (st->st_ctime == bsdtar->newer_ctime_sec && ARCHIVE_STAT_CTIME_NANOS(st) <= bsdtar->newer_ctime_nsec) continue; /* Too old, skip it. */ } if (bsdtar->newer_mtime_sec > 0) { if (st->st_mtime < bsdtar->newer_mtime_sec) continue; /* Too old, skip it. */ if (st->st_mtime == bsdtar->newer_mtime_sec && ARCHIVE_STAT_MTIME_NANOS(st) <= bsdtar->newer_mtime_nsec) continue; /* Too old, skip it. */ } /* * Note that pattern exclusions are checked before * pathname rewrites are handled. This gives more * control over exclusions, since rewrites always lose * information. (For example, consider a rewrite * s/foo[0-9]/foo/. If we check exclusions after the * rewrite, there would be no way to exclude foo1/bar * while allowing foo2/bar.) */ if (excluded(bsdtar, archive_entry_pathname(entry))) continue; /* Excluded by a pattern test. */ if (mode == 't') { /* Perversely, gtar uses -O to mean "send to stderr" * when used with -t. */ out = bsdtar->option_stdout ? stderr : stdout; /* * TODO: Provide some reasonable way to * preview rewrites. gtar always displays * the unedited path in -t output, which means * you cannot easily preview rewrites. */ if (bsdtar->verbose < 2) safe_fprintf(out, "%s", archive_entry_pathname(entry)); else list_item_verbose(bsdtar, out, entry); fflush(out); r = archive_read_data_skip(a); if (r == ARCHIVE_WARN) { fprintf(out, "\n"); bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); } if (r == ARCHIVE_RETRY) { fprintf(out, "\n"); bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); } if (r == ARCHIVE_FATAL) { fprintf(out, "\n"); bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); bsdtar->return_value = 1; break; } fprintf(out, "\n"); } else { /* Note: some rewrite failures prevent extraction. */ if (edit_pathname(bsdtar, entry)) continue; /* Excluded by a rewrite failure. */ if (bsdtar->option_interactive && !yes("extract '%s'", archive_entry_pathname(entry))) continue; /* * Format here is from SUSv2, including the * deferred '\n'. */ if (bsdtar->verbose) { safe_fprintf(stderr, "x %s", archive_entry_pathname(entry)); fflush(stderr); } /* Tell the SIGINFO-handler code what we're doing. */ siginfo_setinfo(bsdtar, "extracting", archive_entry_pathname(entry), 0); siginfo_printinfo(bsdtar, 0); if (bsdtar->option_stdout) r = archive_read_data_into_fd(a, 1); else r = archive_read_extract(a, entry, bsdtar->extract_flags); if (r != ARCHIVE_OK) { if (!bsdtar->verbose) safe_fprintf(stderr, "%s", archive_entry_pathname(entry)); safe_fprintf(stderr, ": %s", archive_error_string(a)); if (!bsdtar->verbose) fprintf(stderr, "\n"); bsdtar->return_value = 1; } if (bsdtar->verbose) fprintf(stderr, "\n"); if (r == ARCHIVE_FATAL) break; } } if (bsdtar->verbose > 2) fprintf(stdout, "Archive Format: %s, Compression: %s\n", archive_format_name(a), archive_compression_name(a)); archive_read_finish(a); } /* * Display information about the current file. * * The format here roughly duplicates the output of 'ls -l'. * This is based on SUSv2, where 'tar tv' is documented as * listing additional information in an "unspecified format," * and 'pax -l' is documented as using the same format as 'ls -l'. */ static void list_item_verbose(struct bsdtar *bsdtar, FILE *out, struct archive_entry *entry) { const struct stat *st; char tmp[100]; size_t w; const char *p; const char *fmt; time_t tim; static time_t now; st = archive_entry_stat(entry); /* * We avoid collecting the entire list in memory at once by * listing things as we see them. However, that also means we can't * just pre-compute the field widths. Instead, we start with guesses * and just widen them as necessary. These numbers are completely * arbitrary. */ if (!bsdtar->u_width) { bsdtar->u_width = 6; bsdtar->gs_width = 13; } if (!now) time(&now); fprintf(out, "%s %d ", archive_entry_strmode(entry), (int)(st->st_nlink)); /* Use uname if it's present, else uid. */ p = archive_entry_uname(entry); if ((p == NULL) || (*p == '\0')) { sprintf(tmp, "%lu ", (unsigned long)st->st_uid); p = tmp; } w = strlen(p); if (w > bsdtar->u_width) bsdtar->u_width = w; fprintf(out, "%-*s ", (int)bsdtar->u_width, p); /* Use gname if it's present, else gid. */ p = archive_entry_gname(entry); if (p != NULL && p[0] != '\0') { fprintf(out, "%s", p); w = strlen(p); } else { sprintf(tmp, "%lu", (unsigned long)st->st_gid); w = strlen(tmp); fprintf(out, "%s", tmp); } /* * Print device number or file size, right-aligned so as to make * total width of group and devnum/filesize fields be gs_width. * If gs_width is too small, grow it. */ if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) { sprintf(tmp, "%lu,%lu", (unsigned long)major(st->st_rdev), (unsigned long)minor(st->st_rdev)); /* ls(1) also casts here. */ } else { /* * Note the use of platform-dependent macros to format * the filesize here. We need the format string and the * corresponding type for the cast. */ sprintf(tmp, BSDTAR_FILESIZE_PRINTF, (BSDTAR_FILESIZE_TYPE)st->st_size); } if (w + strlen(tmp) >= bsdtar->gs_width) bsdtar->gs_width = w+strlen(tmp)+1; fprintf(out, "%*s", (int)(bsdtar->gs_width - w), tmp); /* Format the time using 'ls -l' conventions. */ tim = (time_t)st->st_mtime; +#ifdef _WIN32 + /* Windows' strftime function does not support %e format. */ + if (abs(tim - now) > (365/2)*86400) + fmt = bsdtar->day_first ? "%d %b %Y" : "%b %d %Y"; + else + fmt = bsdtar->day_first ? "%d %b %H:%M" : "%b %d %H:%M"; +#else if (abs(tim - now) > (365/2)*86400) fmt = bsdtar->day_first ? "%e %b %Y" : "%b %e %Y"; else fmt = bsdtar->day_first ? "%e %b %H:%M" : "%b %e %H:%M"; +#endif strftime(tmp, sizeof(tmp), fmt, localtime(&tim)); fprintf(out, " %s ", tmp); safe_fprintf(out, "%s", archive_entry_pathname(entry)); /* Extra information for links. */ if (archive_entry_hardlink(entry)) /* Hard link */ safe_fprintf(out, " link to %s", archive_entry_hardlink(entry)); else if (S_ISLNK(st->st_mode)) /* Symbolic link */ safe_fprintf(out, " -> %s", archive_entry_symlink(entry)); } diff --git a/usr.bin/tar/siginfo.c b/usr.bin/tar/siginfo.c index f0269a10f67d..e0881ac1a1db 100644 --- a/usr.bin/tar/siginfo.c +++ b/usr.bin/tar/siginfo.c @@ -1,147 +1,151 @@ /*- * Copyright 2008 Colin Percival * 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$"); #include #include #include #include #include #include "bsdtar.h" /* Is there a pending SIGINFO or SIGUSR1? */ static volatile sig_atomic_t siginfo_received = 0; struct siginfo_data { /* What sort of operation are we doing? */ char * oper; /* What path are we handling? */ char * path; /* How large is the archive entry? */ int64_t size; /* Old signal handlers. */ #ifdef SIGINFO void (*siginfo_old)(int); #endif void (*sigusr1_old)(int); }; static void siginfo_handler(int sig); /* Handler for SIGINFO / SIGUSR1. */ static void siginfo_handler(int sig) { (void)sig; /* UNUSED */ /* Record that SIGINFO or SIGUSR1 has been received. */ siginfo_received = 1; } void siginfo_init(struct bsdtar *bsdtar) { /* Allocate space for internal structure. */ if ((bsdtar->siginfo = malloc(sizeof(struct siginfo_data))) == NULL) bsdtar_errc(bsdtar, 1, errno, "malloc failed"); /* Set the strings to NULL so that free() is safe. */ bsdtar->siginfo->path = bsdtar->siginfo->oper = NULL; #ifdef SIGINFO /* We want to catch SIGINFO, if it exists. */ bsdtar->siginfo->siginfo_old = signal(SIGINFO, siginfo_handler); #endif +#ifdef SIGUSR1 /* ... and treat SIGUSR1 the same way as SIGINFO. */ bsdtar->siginfo->sigusr1_old = signal(SIGUSR1, siginfo_handler); +#endif } void siginfo_setinfo(struct bsdtar *bsdtar, const char * oper, const char * path, int64_t size) { /* Free old operation and path strings. */ free(bsdtar->siginfo->oper); free(bsdtar->siginfo->path); /* Duplicate strings and store entry size. */ if ((bsdtar->siginfo->oper = strdup(oper)) == NULL) bsdtar_errc(bsdtar, 1, errno, "Cannot strdup"); if ((bsdtar->siginfo->path = strdup(path)) == NULL) bsdtar_errc(bsdtar, 1, errno, "Cannot strdup"); bsdtar->siginfo->size = size; } void siginfo_printinfo(struct bsdtar *bsdtar, off_t progress) { /* If there's a signal to handle and we know what we're doing... */ if ((siginfo_received == 1) && (bsdtar->siginfo->path != NULL) && (bsdtar->siginfo->oper != NULL)) { if (bsdtar->verbose) fprintf(stderr, "\n"); if (bsdtar->siginfo->size > 0) { safe_fprintf(stderr, "%s %s (%ju / %" PRId64 ")", bsdtar->siginfo->oper, bsdtar->siginfo->path, (uintmax_t)progress, bsdtar->siginfo->size); } else { safe_fprintf(stderr, "%s %s", bsdtar->siginfo->oper, bsdtar->siginfo->path); } if (!bsdtar->verbose) fprintf(stderr, "\n"); siginfo_received = 0; } } void siginfo_done(struct bsdtar *bsdtar) { #ifdef SIGINFO /* Restore old SIGINFO handler. */ signal(SIGINFO, bsdtar->siginfo->siginfo_old); #endif +#ifdef SIGUSR1 /* And the old SIGUSR1 handler, too. */ signal(SIGUSR1, bsdtar->siginfo->sigusr1_old); +#endif /* Free strings. */ free(bsdtar->siginfo->path); free(bsdtar->siginfo->oper); /* Free internal data structure. */ free(bsdtar->siginfo); } diff --git a/usr.bin/tar/test/main.c b/usr.bin/tar/test/main.c index 7f114ecf0159..ad434aa5ac3d 100644 --- a/usr.bin/tar/test/main.c +++ b/usr.bin/tar/test/main.c @@ -1,1094 +1,1121 @@ /* * 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. */ /* * Various utility routines useful for test programs. * Each test program is linked against this file. */ #include "test.h" #include #include #include #include /* * This same file is used pretty much verbatim for all test harnesses. * * The next few lines are the only differences. */ #define PROGRAM "bsdtar" /* Name of program being tested. */ #define ENVBASE "BSDTAR" /* Prefix for environment variables. */ #undef EXTRA_DUMP /* How to dump extra data */ /* How to generate extra version info. */ #define EXTRA_VERSION (systemf("%s --version", testprog) ? "" : "") __FBSDID("$FreeBSD$"); /* * "list.h" is simply created by "grep DEFINE_TEST"; it has * a line like * DEFINE_TEST(test_function) * for each test. * Include it here with a suitable DEFINE_TEST to declare all of the * test functions. */ #undef DEFINE_TEST #define DEFINE_TEST(name) void name(void); #ifdef LIST_H #include LIST_H #else #include "list.h" #endif /* Interix doesn't define these in a standard header. */ #if __INTERIX__ extern char *optarg; extern int optind; #endif /* Enable core dump on failure. */ static int dump_on_failure = 0; /* Default is to remove temp dirs for successful tests. */ static int keep_temp_files = 0; /* Default is to print some basic information about each test. */ static int quiet_flag = 0; /* Default is to summarize repeated failures. */ static int verbose = 0; /* Cumulative count of component failures. */ static int failures = 0; /* Cumulative count of skipped component tests. */ static int skips = 0; /* Cumulative count of assertions. */ static int assertions = 0; /* Directory where uuencoded reference files can be found. */ static const char *refdir; /* * My own implementation of the standard assert() macro emits the * message in the same format as GCC (file:line: message). * It also includes some additional useful information. * This makes it a lot easier to skim through test failures in * Emacs. ;-) * * It also supports a few special features specifically to simplify * test harnesses: * failure(fmt, args) -- Stores a text string that gets * printed if the following assertion fails, good for * explaining subtle tests. */ static char msg[4096]; /* * For each test source file, we remember how many times each * failure was reported. */ static const char *failed_filename = NULL; static struct line { int line; int count; } failed_lines[1000]; /* * Count this failure; return the number of previous failures. */ static int previous_failures(const char *filename, int line) { unsigned int i; int count; if (failed_filename == NULL || strcmp(failed_filename, filename) != 0) memset(failed_lines, 0, sizeof(failed_lines)); failed_filename = filename; for (i = 0; i < sizeof(failed_lines)/sizeof(failed_lines[0]); i++) { if (failed_lines[i].line == line) { count = failed_lines[i].count; failed_lines[i].count++; return (count); } if (failed_lines[i].line == 0) { failed_lines[i].line = line; failed_lines[i].count = 1; return (0); } } return (0); } /* * Copy arguments into file-local variables. */ static const char *test_filename; static int test_line; static void *test_extra; void test_setup(const char *filename, int line) { test_filename = filename; test_line = line; } /* * Inform user that we're skipping a test. */ void test_skipping(const char *fmt, ...) { va_list ap; if (previous_failures(test_filename, test_line)) return; va_start(ap, fmt); fprintf(stderr, " *** SKIPPING: "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); ++skips; } /* Common handling of failed tests. */ static void report_failure(void *extra) { if (msg[0] != '\0') { fprintf(stderr, " Description: %s\n", msg); msg[0] = '\0'; } #ifdef EXTRA_DUMP if (extra != NULL) fprintf(stderr, " detail: %s\n", EXTRA_DUMP(extra)); #else (void)extra; /* UNUSED */ #endif if (dump_on_failure) { fprintf(stderr, " *** forcing core dump so failure can be debugged ***\n"); *(char *)(NULL) = 0; exit(1); } } /* * Summarize repeated failures in the just-completed test file. * The reports above suppress multiple failures from the same source * line; this reports on any tests that did fail multiple times. */ static int summarize_comparator(const void *a0, const void *b0) { const struct line *a = a0, *b = b0; if (a->line == 0 && b->line == 0) return (0); if (a->line == 0) return (1); if (b->line == 0) return (-1); return (a->line - b->line); } static void summarize(void) { unsigned int i; qsort(failed_lines, sizeof(failed_lines)/sizeof(failed_lines[0]), sizeof(failed_lines[0]), summarize_comparator); for (i = 0; i < sizeof(failed_lines)/sizeof(failed_lines[0]); i++) { if (failed_lines[i].line == 0) break; if (failed_lines[i].count > 1) fprintf(stderr, "%s:%d: Failed %d times\n", failed_filename, failed_lines[i].line, failed_lines[i].count); } /* Clear the failure history for the next file. */ memset(failed_lines, 0, sizeof(failed_lines)); } /* Set up a message to display only after a test fails. */ void failure(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsprintf(msg, fmt, ap); va_end(ap); } /* Generic assert() just displays the failed condition. */ int test_assert(const char *file, int line, int value, const char *condition, void *extra) { ++assertions; if (value) { msg[0] = '\0'; return (value); } failures ++; if (!verbose && previous_failures(file, line)) return (value); fprintf(stderr, "%s:%d: Assertion failed\n", file, line); fprintf(stderr, " Condition: %s\n", condition); report_failure(extra); return (value); } /* assertEqualInt() displays the values of the two integers. */ int test_assert_equal_int(const char *file, int line, int v1, const char *e1, int v2, const char *e2, void *extra) { ++assertions; if (v1 == v2) { msg[0] = '\0'; return (1); } failures ++; if (!verbose && previous_failures(file, line)) return (0); fprintf(stderr, "%s:%d: Assertion failed: Ints not equal\n", file, line); fprintf(stderr, " %s=%d\n", e1, v1); fprintf(stderr, " %s=%d\n", e2, v2); report_failure(extra); return (0); } static void strdump(const char *p) { if (p == NULL) { fprintf(stderr, "(null)"); return; } fprintf(stderr, "\""); while (*p != '\0') { unsigned int c = 0xff & *p++; switch (c) { case '\a': fprintf(stderr, "\a"); break; case '\b': fprintf(stderr, "\b"); break; case '\n': fprintf(stderr, "\n"); break; case '\r': fprintf(stderr, "\r"); break; default: if (c >= 32 && c < 127) fprintf(stderr, "%c", c); else fprintf(stderr, "\\x%02X", c); } } fprintf(stderr, "\""); } /* assertEqualString() displays the values of the two strings. */ int test_assert_equal_string(const char *file, int line, const char *v1, const char *e1, const char *v2, const char *e2, void *extra) { ++assertions; if (v1 == NULL || v2 == NULL) { if (v1 == v2) { msg[0] = '\0'; return (1); } } else if (strcmp(v1, v2) == 0) { msg[0] = '\0'; return (1); } failures ++; if (!verbose && previous_failures(file, line)) return (0); fprintf(stderr, "%s:%d: Assertion failed: Strings not equal\n", file, line); fprintf(stderr, " %s = ", e1); strdump(v1); fprintf(stderr, " (length %d)\n", v1 == NULL ? 0 : (int)strlen(v1)); fprintf(stderr, " %s = ", e2); strdump(v2); fprintf(stderr, " (length %d)\n", v2 == NULL ? 0 : (int)strlen(v2)); report_failure(extra); return (0); } static void wcsdump(const wchar_t *w) { if (w == NULL) { fprintf(stderr, "(null)"); return; } fprintf(stderr, "\""); while (*w != L'\0') { unsigned int c = *w++; if (c >= 32 && c < 127) fprintf(stderr, "%c", c); else if (c < 256) fprintf(stderr, "\\x%02X", c); else if (c < 0x10000) fprintf(stderr, "\\u%04X", c); else fprintf(stderr, "\\U%08X", c); } fprintf(stderr, "\""); } /* assertEqualWString() displays the values of the two strings. */ int test_assert_equal_wstring(const char *file, int line, const wchar_t *v1, const char *e1, const wchar_t *v2, const char *e2, void *extra) { ++assertions; if (v1 == NULL) { if (v2 == NULL) { msg[0] = '\0'; return (1); } } else if (v2 == NULL) { if (v1 == NULL) { msg[0] = '\0'; return (1); } } else if (wcscmp(v1, v2) == 0) { msg[0] = '\0'; return (1); } failures ++; if (!verbose && previous_failures(file, line)) return (0); fprintf(stderr, "%s:%d: Assertion failed: Unicode strings not equal\n", file, line); fprintf(stderr, " %s = ", e1); wcsdump(v1); fprintf(stderr, "\n"); fprintf(stderr, " %s = ", e2); wcsdump(v2); fprintf(stderr, "\n"); report_failure(extra); return (0); } /* * Pretty standard hexdump routine. As a bonus, if ref != NULL, then * any bytes in p that differ from ref will be highlighted with '_' * before and after the hex value. */ static void hexdump(const char *p, const char *ref, size_t l, size_t offset) { size_t i, j; char sep; for(i=0; i < l; i+=16) { fprintf(stderr, "%04x", (int)(i + offset)); sep = ' '; for (j = 0; j < 16 && i + j < l; j++) { if (ref != NULL && p[i + j] != ref[i + j]) sep = '_'; fprintf(stderr, "%c%02x", sep, 0xff & (int)p[i+j]); if (ref != NULL && p[i + j] == ref[i + j]) sep = ' '; } for (; j < 16; j++) { fprintf(stderr, "%c ", sep); sep = ' '; } fprintf(stderr, "%c", sep); for (j=0; j < 16 && i + j < l; j++) { int c = p[i + j]; if (c >= ' ' && c <= 126) fprintf(stderr, "%c", c); else fprintf(stderr, "."); } fprintf(stderr, "\n"); } } /* assertEqualMem() displays the values of the two memory blocks. */ /* TODO: For long blocks, hexdump the first bytes that actually differ. */ int test_assert_equal_mem(const char *file, int line, const char *v1, const char *e1, const char *v2, const char *e2, size_t l, const char *ld, void *extra) { ++assertions; if (v1 == NULL || v2 == NULL) { if (v1 == v2) { msg[0] = '\0'; return (1); } } else if (memcmp(v1, v2, l) == 0) { msg[0] = '\0'; return (1); } failures ++; if (!verbose && previous_failures(file, line)) return (0); fprintf(stderr, "%s:%d: Assertion failed: memory not equal\n", file, line); fprintf(stderr, " size %s = %d\n", ld, (int)l); fprintf(stderr, " Dump of %s\n", e1); hexdump(v1, v2, l < 32 ? l : 32, 0); fprintf(stderr, " Dump of %s\n", e2); hexdump(v2, v1, l < 32 ? l : 32, 0); fprintf(stderr, "\n"); report_failure(extra); return (0); } int test_assert_empty_file(const char *f1fmt, ...) { char buff[1024]; char f1[1024]; struct stat st; va_list ap; ssize_t s; int fd; va_start(ap, f1fmt); vsprintf(f1, f1fmt, ap); va_end(ap); if (stat(f1, &st) != 0) { fprintf(stderr, "%s:%d: Could not stat: %s\n", test_filename, test_line, f1); failures ++; report_failure(NULL); return (0); } if (st.st_size == 0) return (1); failures ++; if (!verbose && previous_failures(test_filename, test_line)) return (0); fprintf(stderr, "%s:%d: File not empty: %s\n", test_filename, test_line, f1); fprintf(stderr, " File size: %d\n", (int)st.st_size); fprintf(stderr, " Contents:\n"); fd = open(f1, O_RDONLY); if (fd < 0) { fprintf(stderr, " Unable to open %s\n", f1); } else { s = ((off_t)sizeof(buff) < st.st_size) ? (ssize_t)sizeof(buff) : (ssize_t)st.st_size; s = read(fd, buff, s); hexdump(buff, NULL, s, 0); + close(fd); } report_failure(NULL); return (0); } int test_assert_non_empty_file(const char *f1fmt, ...) { char f1[1024]; struct stat st; va_list ap; va_start(ap, f1fmt); vsprintf(f1, f1fmt, ap); va_end(ap); if (stat(f1, &st) != 0) { fprintf(stderr, "%s:%d: Could not stat: %s\n", test_filename, test_line, f1); report_failure(NULL); failures++; return (0); } if (st.st_size != 0) return (1); failures ++; if (!verbose && previous_failures(test_filename, test_line)) return (0); fprintf(stderr, "%s:%d: File empty: %s\n", test_filename, test_line, f1); report_failure(NULL); return (0); } /* assertEqualFile() asserts that two files have the same contents. */ /* TODO: hexdump the first bytes that actually differ. */ int test_assert_equal_file(const char *f1, const char *f2pattern, ...) { char f2[1024]; va_list ap; char buff1[1024]; char buff2[1024]; int fd1, fd2; int n1, n2; va_start(ap, f2pattern); vsprintf(f2, f2pattern, ap); va_end(ap); fd1 = open(f1, O_RDONLY); fd2 = open(f2, O_RDONLY); for (;;) { n1 = read(fd1, buff1, sizeof(buff1)); n2 = read(fd2, buff2, sizeof(buff2)); if (n1 != n2) break; - if (n1 == 0 && n2 == 0) + if (n1 == 0 && n2 == 0) { + close(fd1); + close(fd2); return (1); + } if (memcmp(buff1, buff2, n1) != 0) break; } + close(fd1); + close(fd2); failures ++; if (!verbose && previous_failures(test_filename, test_line)) return (0); fprintf(stderr, "%s:%d: Files are not identical\n", test_filename, test_line); fprintf(stderr, " file1=\"%s\"\n", f1); fprintf(stderr, " file2=\"%s\"\n", f2); report_failure(test_extra); return (0); } int test_assert_file_exists(const char *fpattern, ...) { char f[1024]; va_list ap; va_start(ap, fpattern); vsprintf(f, fpattern, ap); va_end(ap); if (!access(f, F_OK)) return (1); if (!previous_failures(test_filename, test_line)) { fprintf(stderr, "%s:%d: File doesn't exist\n", test_filename, test_line); fprintf(stderr, " file=\"%s\"\n", f); report_failure(test_extra); } return (0); } int test_assert_file_not_exists(const char *fpattern, ...) { char f[1024]; va_list ap; va_start(ap, fpattern); vsprintf(f, fpattern, ap); va_end(ap); if (access(f, F_OK)) return (1); if (!previous_failures(test_filename, test_line)) { fprintf(stderr, "%s:%d: File exists and shouldn't\n", test_filename, test_line); fprintf(stderr, " file=\"%s\"\n", f); report_failure(test_extra); } return (0); } /* assertFileContents() asserts the contents of a file. */ int test_assert_file_contents(const void *buff, int s, const char *fpattern, ...) { char f[1024]; va_list ap; char *contents; int fd; int n; va_start(ap, fpattern); vsprintf(f, fpattern, ap); va_end(ap); fd = open(f, O_RDONLY); if (fd < 0) { failures ++; if (!previous_failures(test_filename, test_line)) { fprintf(stderr, "%s:%d: File doesn't exist: %s\n", test_filename, test_line, f); report_failure(test_extra); } return (0); } contents = malloc(s * 2); n = read(fd, contents, s * 2); + close(fd); if (n == s && memcmp(buff, contents, s) == 0) { free(contents); return (1); } failures ++; if (!previous_failures(test_filename, test_line)) { fprintf(stderr, "%s:%d: File contents don't match\n", test_filename, test_line); fprintf(stderr, " file=\"%s\"\n", f); if (n > 0) hexdump(contents, buff, n, 0); else { fprintf(stderr, " File empty, contents should be:\n"); hexdump(buff, NULL, s, 0); } report_failure(test_extra); } free(contents); return (0); } /* * Call standard system() call, but build up the command line using * sprintf() conventions. */ int systemf(const char *fmt, ...) { char buff[8192]; va_list ap; int r; va_start(ap, fmt); vsprintf(buff, fmt, ap); r = system(buff); va_end(ap); return (r); } /* * Slurp a file into memory for ease of comparison and testing. * Returns size of file in 'sizep' if non-NULL, null-terminates * data in memory for ease of use. */ char * slurpfile(size_t * sizep, const char *fmt, ...) { char filename[8192]; struct stat st; va_list ap; char *p; ssize_t bytes_read; int fd; int r; va_start(ap, fmt); vsprintf(filename, fmt, ap); va_end(ap); fd = open(filename, O_RDONLY); if (fd < 0) { /* Note: No error; non-existent file is okay here. */ return (NULL); } r = fstat(fd, &st); if (r != 0) { fprintf(stderr, "Can't stat file %s\n", filename); close(fd); return (NULL); } p = malloc(st.st_size + 1); if (p == NULL) { fprintf(stderr, "Can't allocate %ld bytes of memory to read file %s\n", (long int)st.st_size, filename); close(fd); return (NULL); } bytes_read = read(fd, p, st.st_size); if (bytes_read < st.st_size) { fprintf(stderr, "Can't read file %s\n", filename); close(fd); free(p); return (NULL); } p[st.st_size] = '\0'; if (sizep != NULL) *sizep = (size_t)st.st_size; close(fd); return (p); } /* * "list.h" is automatically generated; it just has a lot of lines like: * DEFINE_TEST(function_name) * It's used above to declare all of the test functions. * We reuse it here to define a list of all tests (functions and names). */ #undef DEFINE_TEST #define DEFINE_TEST(n) { n, #n }, struct { void (*func)(void); const char *name; } tests[] = { #ifdef LIST_H #include LIST_H #else #include "list.h" #endif }; /* * Each test is run in a private work dir. Those work dirs * do have consistent and predictable names, in case a group * of tests need to collaborate. However, there is no provision * for requiring that tests run in a certain order. */ static int test_run(int i, const char *tmpdir) { int failures_before = failures; if (!quiet_flag) { printf("%d: %s\n", i, tests[i].name); fflush(stdout); } /* * Always explicitly chdir() in case the last test moved us to * a strange place. */ if (chdir(tmpdir)) { fprintf(stderr, "ERROR: Couldn't chdir to temp dir %s\n", tmpdir); exit(1); } /* Create a temp directory for this specific test. */ if (mkdir(tests[i].name, 0755)) { fprintf(stderr, "ERROR: Couldn't create temp dir ``%s''\n", tests[i].name); exit(1); } /* Chdir() to that work directory. */ if (chdir(tests[i].name)) { fprintf(stderr, "ERROR: Couldn't chdir to temp dir ``%s''\n", tests[i].name); exit(1); } /* Explicitly reset the locale before each test. */ setlocale(LC_ALL, "C"); /* Run the actual test. */ (*tests[i].func)(); /* Summarize the results of this test. */ summarize(); /* If there were no failures, we can remove the work dir. */ if (failures == failures_before) { if (!keep_temp_files && chdir(tmpdir) == 0) { +#ifdef _WIN32 + systemf("rmdir /S /Q %s", tests[i].name); +#else systemf("rm -rf %s", tests[i].name); +#endif } } /* Return appropriate status. */ return (failures == failures_before ? 0 : 1); } static void usage(const char *program) { static const int limit = sizeof(tests) / sizeof(tests[0]); int i; printf("Usage: %s [options] ...\n", program); printf("Default is to run all tests.\n"); printf("Otherwise, specify the numbers of the tests you wish to run.\n"); printf("Options:\n"); printf(" -d Dump core after any failure, for debugging.\n"); printf(" -k Keep all temp files.\n"); printf(" Default: temp files for successful tests deleted.\n"); #ifdef PROGRAM printf(" -p Path to executable to be tested.\n"); printf(" Default: path taken from " ENVBASE " environment variable.\n"); #endif printf(" -q Quiet.\n"); printf(" -r Path to dir containing reference files.\n"); printf(" Default: Current directory.\n"); printf(" -v Verbose.\n"); printf("Available tests:\n"); for (i = 0; i < limit; i++) printf(" %d: %s\n", i, tests[i].name); exit(1); } #define UUDECODE(c) (((c) - 0x20) & 0x3f) void extract_reference_file(const char *name) { char buff[1024]; FILE *in, *out; sprintf(buff, "%s/%s.uu", refdir, name); in = fopen(buff, "r"); failure("Couldn't open reference file %s", buff); assert(in != NULL); if (in == NULL) return; /* Read up to and including the 'begin' line. */ for (;;) { if (fgets(buff, sizeof(buff), in) == NULL) { /* TODO: This is a failure. */ return; } if (memcmp(buff, "begin ", 6) == 0) break; } /* Now, decode the rest and write it. */ /* Not a lot of error checking here; the input better be right. */ out = fopen(name, "w"); while (fgets(buff, sizeof(buff), in) != NULL) { char *p = buff; int bytes; if (memcmp(buff, "end", 3) == 0) break; bytes = UUDECODE(*p++); while (bytes > 0) { int n = 0; /* Write out 1-3 bytes from that. */ if (bytes > 0) { n = UUDECODE(*p++) << 18; n |= UUDECODE(*p++) << 12; fputc(n >> 16, out); --bytes; } if (bytes > 0) { n |= UUDECODE(*p++) << 6; fputc((n >> 8) & 0xFF, out); --bytes; } if (bytes > 0) { n |= UUDECODE(*p++); fputc(n & 0xFF, out); --bytes; } } } fclose(out); fclose(in); } int main(int argc, char **argv) { static const int limit = sizeof(tests) / sizeof(tests[0]); int i, tests_run = 0, tests_failed = 0, opt; time_t now; char *refdir_alloc = NULL; +#ifdef _WIN32 + char *testprg; +#endif const char *opt_arg, *progname, *p; char tmpdir[256]; char tmpdir_timestamp[256]; (void)argc; /* UNUSED */ /* * Name of this program, used to build root of our temp directory * tree. */ progname = p = argv[0]; while (*p != '\0') { - if (*p == '/') + /* Support \ or / dir separators for Windows compat. */ + if (*p == '/' || *p == '\\') progname = p + 1; ++p; } #ifdef PROGRAM /* Get the target program from environment, if available. */ testprog = getenv(ENVBASE); #endif /* Allow -d to be controlled through the environment. */ if (getenv(ENVBASE "_DEBUG") != NULL) dump_on_failure = 1; /* Get the directory holding test files from environment. */ refdir = getenv(ENVBASE "_TEST_FILES"); /* * Parse options, without using getopt(), which isn't available * on all platforms. */ ++argv; /* Skip program name */ while (*argv != NULL) { if (**argv != '-') break; p = *argv++; ++p; /* Skip '-' */ while (*p != '\0') { opt = *p++; opt_arg = NULL; /* If 'opt' takes an argument, parse that. */ if (opt == 'p' || opt == 'r') { if (*p != '\0') opt_arg = p; else if (*argv == NULL) { fprintf(stderr, "Option -%c requires argument.\n", opt); usage(progname); } else opt_arg = *argv++; p = ""; /* End of this option word. */ } /* Now, handle the option. */ switch (opt) { case 'd': dump_on_failure = 1; break; case 'k': keep_temp_files = 1; break; case 'p': #ifdef PROGRAM testprog = opt_arg; #else usage(progname); #endif break; case 'q': quiet_flag++; break; case 'r': refdir = opt_arg; break; case 'v': verbose = 1; break; case '?': default: usage(progname); } } } /* * Sanity-check that our options make sense. */ #ifdef PROGRAM if (testprog == NULL) usage(progname); #endif +#ifdef _WIN32 + /* + * command.com cannot accept the command used '/' with drive + * name such as c:/xxx/command.exe when use '|' pipe handling. + */ + testprg = strdup(testprog); + for (i = 0; testprg[i] != '\0'; i++) { + if (testprg[i] == '/') + testprg[i] = '\\'; + } + testprog = testprg; +#endif /* * Create a temp directory for the following tests. * Include the time the tests started as part of the name, * to make it easier to track the results of multiple tests. */ now = time(NULL); for (i = 0; i < 1000; i++) { strftime(tmpdir_timestamp, sizeof(tmpdir_timestamp), "%Y-%m-%dT%H.%M.%S", localtime(&now)); sprintf(tmpdir, "/tmp/%s.%s-%03d", progname, tmpdir_timestamp, i); if (mkdir(tmpdir,0755) == 0) break; if (errno == EEXIST) continue; fprintf(stderr, "ERROR: Unable to create temp directory %s\n", tmpdir); exit(1); } /* * If the user didn't specify a directory for locating * reference files, use the current directory for that. */ if (refdir == NULL) { char *q; systemf("/bin/pwd > %s/refdir", tmpdir); q = slurpfile(NULL, "%s/refdir", tmpdir); refdir = refdir_alloc = q; q += strlen(refdir); while (q[-1] == '\n') { --q; *q = '\0'; } systemf("rm %s/refdir", tmpdir); } /* * Banner with basic information. */ if (!quiet_flag) { printf("Running tests in: %s\n", tmpdir); printf("Reference files will be read from: %s\n", refdir); #ifdef PROGRAM printf("Running tests on: %s\n", testprog); #endif printf("Exercising: "); fflush(stdout); printf("%s\n", EXTRA_VERSION); } /* * Run some or all of the individual tests. */ if (*argv == NULL) { /* Default: Run all tests. */ for (i = 0; i < limit; i++) { if (test_run(i, tmpdir)) tests_failed++; tests_run++; } } else { while (*(argv) != NULL) { i = atoi(*argv); if (**argv < '0' || **argv > '9' || i < 0 || i >= limit) { printf("*** INVALID Test %s\n", *argv); usage(progname); } else { if (test_run(i, tmpdir)) tests_failed++; tests_run++; } argv++; } } /* * Report summary statistics. */ if (!quiet_flag) { printf("\n"); printf("%d of %d tests reported failures\n", tests_failed, tests_run); printf(" Total of %d assertions checked.\n", assertions); printf(" Total of %d assertions failed.\n", failures); printf(" Total of %d assertions skipped.\n", skips); } free(refdir_alloc); /* If the final tmpdir is empty, we can remove it. */ /* This should be the usual case when all tests succeed. */ rmdir(tmpdir); return (tests_failed); } diff --git a/usr.bin/tar/test/test.h b/usr.bin/tar/test/test.h index ab568c252a4a..27a693520114 100644 --- a/usr.bin/tar/test/test.h +++ b/usr.bin/tar/test/test.h @@ -1,154 +1,160 @@ /* * Copyright (c) 2003-2006 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. * * $FreeBSD$ */ /* Every test program should #include "test.h" as the first thing. */ /* * The goal of this file (and the matching test.c) is to * simplify the very repetitive test-*.c test programs. */ #if defined(HAVE_CONFIG_H) /* Most POSIX platforms use the 'configure' script to build config.h */ #include "config.h" #elif defined(__FreeBSD__) /* Building as part of FreeBSD system requires a pre-built config.h. */ #include "config_freebsd.h" #elif defined(_WIN32) /* Win32 can't run the 'configure' script. */ #include "config_windows.h" #else /* Warn if the library hasn't been (automatically or manually) configured. */ #error Oops: No config.h and no pre-built configuration in test.h. #endif +#ifndef _WIN32 #include +#else +#define dirent direct +#include "../bsdtar_windows.h" +#include +#endif #include #include #include #include #include #include #ifndef _WIN32 #include #endif #include #ifdef USE_DMALLOC #include #endif /* No non-FreeBSD platform will have __FBSDID, so just define it here. */ #ifdef __FreeBSD__ #include /* For __FBSDID */ #else #define __FBSDID(a) /* null */ #endif /* * Redefine DEFINE_TEST for use in defining the test functions. */ #undef DEFINE_TEST #define DEFINE_TEST(name) void name(void); void name(void) /* An implementation of the standard assert() macro */ #define assert(e) test_assert(__FILE__, __LINE__, (e), #e, NULL) /* Assert two integers are the same. Reports value of each one if not. */ #define assertEqualInt(v1,v2) \ test_assert_equal_int(__FILE__, __LINE__, (v1), #v1, (v2), #v2, NULL) /* Assert two strings are the same. Reports value of each one if not. */ #define assertEqualString(v1,v2) \ test_assert_equal_string(__FILE__, __LINE__, (v1), #v1, (v2), #v2, NULL) /* As above, but v1 and v2 are wchar_t * */ #define assertEqualWString(v1,v2) \ test_assert_equal_wstring(__FILE__, __LINE__, (v1), #v1, (v2), #v2, NULL) /* As above, but raw blocks of bytes. */ #define assertEqualMem(v1, v2, l) \ test_assert_equal_mem(__FILE__, __LINE__, (v1), #v1, (v2), #v2, (l), #l, NULL) /* Assert two files are the same; allow printf-style expansion of second name. * See below for comments about variable arguments here... */ #define assertEqualFile \ test_setup(__FILE__, __LINE__);test_assert_equal_file /* Assert that a file is empty; supports printf-style arguments. */ #define assertEmptyFile \ test_setup(__FILE__, __LINE__);test_assert_empty_file /* Assert that a file is not empty; supports printf-style arguments. */ #define assertNonEmptyFile \ test_setup(__FILE__, __LINE__);test_assert_non_empty_file /* Assert that a file exists; supports printf-style arguments. */ #define assertFileExists \ test_setup(__FILE__, __LINE__);test_assert_file_exists /* Assert that a file exists; supports printf-style arguments. */ #define assertFileNotExists \ test_setup(__FILE__, __LINE__);test_assert_file_not_exists /* Assert that file contents match a string; supports printf-style arguments. */ #define assertFileContents \ test_setup(__FILE__, __LINE__);test_assert_file_contents /* * This would be simple with C99 variadic macros, but I don't want to * require that. Instead, I insert a function call before each * skipping() call to pass the file and line information down. Crude, * but effective. */ #define skipping \ test_setup(__FILE__, __LINE__);test_skipping /* Function declarations. These are defined in test_utility.c. */ void failure(const char *fmt, ...); void test_setup(const char *, int); void test_skipping(const char *fmt, ...); int test_assert(const char *, int, int, const char *, void *); int test_assert_empty_file(const char *, ...); int test_assert_non_empty_file(const char *, ...); int test_assert_equal_file(const char *, const char *, ...); int test_assert_equal_int(const char *, int, int, const char *, int, const char *, void *); int test_assert_equal_string(const char *, int, const char *v1, const char *, const char *v2, const char *, void *); int test_assert_equal_wstring(const char *, int, const wchar_t *v1, const char *, const wchar_t *v2, const char *, void *); int test_assert_equal_mem(const char *, int, const char *, const char *, const char *, const char *, size_t, const char *, void *); int test_assert_file_contents(const void *, int, const char *, ...); int test_assert_file_exists(const char *, ...); int test_assert_file_not_exists(const char *, ...); /* Like sprintf, then system() */ int systemf(const char * fmt, ...); /* Suck file into string allocated via malloc(). Call free() when done. */ /* Supports printf-style args: slurpfile(NULL, "%s/myfile", refdir); */ char *slurpfile(size_t *, const char *fmt, ...); /* Extracts named reference file to the current directory. */ void extract_reference_file(const char *); /* * Special interfaces for program test harness. */ /* Pathname of exe to be tested. */ const char *testprog; diff --git a/usr.bin/tar/test/test_0.c b/usr.bin/tar/test/test_0.c index 7a72af1c8edb..1bafc05a2cad 100644 --- a/usr.bin/tar/test/test_0.c +++ b/usr.bin/tar/test/test_0.c @@ -1,62 +1,67 @@ /*- * 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$"); /* * This first test does basic sanity checks on the environment. For * most of these, we just exit on failure. */ +#ifndef _WIN32 +#define DEV_NULL "/dev/null" +#else +#define DEV_NULL "NUL" +#endif DEFINE_TEST(test_0) { struct stat st; failure("File %s does not exist?!", testprog); if (!assertEqualInt(0, stat(testprog, &st))) exit(1); failure("%s is not executable?!", testprog); if (!assert((st.st_mode & 0111) != 0)) exit(1); /* * Try to succesfully run the program; this requires that * we know some option that will succeed. */ - if (0 == systemf("%s --version >/dev/null", testprog)) { + if (0 == systemf("%s --version >" DEV_NULL, testprog)) { /* This worked. */ - } else if (0 == systemf("%s -W version >/dev/null", testprog)) { + } else if (0 == systemf("%s -W version >" DEV_NULL, testprog)) { /* This worked. */ } else { failure("Unable to successfully run any of the following:\n" " * %s --version\n" " * %s -W version\n", testprog, testprog); assert(0); } /* TODO: Ensure that our reference files are available. */ } diff --git a/usr.bin/tar/test/test_basic.c b/usr.bin/tar/test/test_basic.c index 50be2890c0e3..b454723abd23 100644 --- a/usr.bin/tar/test/test_basic.c +++ b/usr.bin/tar/test/test_basic.c @@ -1,158 +1,185 @@ /*- * 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$"); static void -basic_tar(const char *target, const char *pack_options, const char *unpack_options) +basic_tar(const char *target, const char *pack_options, + const char *unpack_options, const char *flist) { struct stat st, st2; +#ifndef _WIN32 char buff[128]; +#endif int r; assertEqualInt(0, mkdir(target, 0775)); /* Use the tar program to create an archive. */ - r = systemf("%s cf - %s `cat filelist` >%s/archive 2>%s/pack.err", testprog, pack_options, target, target); +#ifndef _WIN32 + r = systemf("%s cf - %s `cat %s` >%s/archive 2>%s/pack.err", testprog, pack_options, flist, target, target); +#else + r = systemf("%s cf - %s %s >%s/archive 2>%s/pack.err", testprog, pack_options, flist, target, target); +#endif failure("Error invoking %s cf -", testprog, pack_options); assertEqualInt(r, 0); chdir(target); /* Verify that nothing went to stderr. */ assertEmptyFile("pack.err"); /* * Use tar to unpack the archive into another directory. */ r = systemf("%s xf archive %s >unpack.out 2>unpack.err", testprog, unpack_options); failure("Error invoking %s xf archive %s", testprog, unpack_options); assertEqualInt(r, 0); /* Verify that nothing went to stderr. */ assertEmptyFile("unpack.err"); /* * Verify unpacked files. */ /* Regular file with 2 links. */ r = lstat("file", &st); failure("Failed to stat file %s/file, errno=%d", target, errno); assertEqualInt(r, 0); if (r == 0) { assert(S_ISREG(st.st_mode)); +#ifndef _WIN32 assertEqualInt(0644, st.st_mode & 0777); +#else + assertEqualInt(0600, st.st_mode & 0700); +#endif assertEqualInt(10, st.st_size); failure("file %s/file", target); assertEqualInt(2, st.st_nlink); } /* Another name for the same file. */ r = lstat("linkfile", &st2); failure("Failed to stat file %s/linkfile, errno=%d", target, errno); assertEqualInt(r, 0); if (r == 0) { assert(S_ISREG(st2.st_mode)); +#ifndef _WIN32 assertEqualInt(0644, st2.st_mode & 0777); +#else + assertEqualInt(0600, st2.st_mode & 0700); +#endif assertEqualInt(10, st2.st_size); failure("file %s/linkfile", target); assertEqualInt(2, st2.st_nlink); /* Verify that the two are really hardlinked. */ assertEqualInt(st.st_dev, st2.st_dev); failure("%s/linkfile and %s/file aren't really hardlinks", target, target); assertEqualInt(st.st_ino, st2.st_ino); } +#ifndef _WIN32 /* Symlink */ r = lstat("symlink", &st); failure("Failed to stat file %s/symlink, errno=%d", target, errno); assertEqualInt(r, 0); if (r == 0) { failure("symlink should be a symlink; actual mode is %o", st.st_mode); assert(S_ISLNK(st.st_mode)); if (S_ISLNK(st.st_mode)) { r = readlink("symlink", buff, sizeof(buff)); assertEqualInt(r, 4); buff[r] = '\0'; assertEqualString(buff, "file"); } } +#endif /* dir */ r = lstat("dir", &st); if (r == 0) { assertEqualInt(r, 0); assert(S_ISDIR(st.st_mode)); +#ifndef _WIN32 assertEqualInt(0775, st.st_mode & 0777); +#else + assertEqualInt(0700, st.st_mode & 0700); +#endif } chdir(".."); } DEFINE_TEST(test_basic) { int fd; int filelist; int oldumask; + const char *flist; oldumask = umask(0); /* * Create an assortment of files on disk. */ filelist = open("filelist", O_CREAT | O_WRONLY, 0644); /* File with 10 bytes content. */ fd = open("file", O_CREAT | O_WRONLY, 0644); assert(fd >= 0); assertEqualInt(10, write(fd, "123456789", 10)); close(fd); write(filelist, "file\n", 5); /* hardlink to above file. */ assertEqualInt(0, link("file", "linkfile")); write(filelist, "linkfile\n", 9); /* Symlink to above file. */ assertEqualInt(0, symlink("file", "symlink")); write(filelist, "symlink\n", 8); /* Directory. */ assertEqualInt(0, mkdir("dir", 0775)); write(filelist, "dir\n", 4); /* All done. */ close(filelist); +#ifndef _WIN32 + flist = "filelist"; +#else + flist = "file linkfile symlink dir"; +#endif /* Archive/dearchive with a variety of options. */ - basic_tar("copy", "", ""); + basic_tar("copy", "", "", flist); /* tar doesn't handle cpio symlinks correctly */ /* basic_tar("copy_odc", "--format=odc", ""); */ - basic_tar("copy_ustar", "--format=ustar", ""); + basic_tar("copy_ustar", "--format=ustar", "", flist); umask(oldumask); } diff --git a/usr.bin/tar/test/test_copy.c b/usr.bin/tar/test/test_copy.c index 7f12f9c89bb2..9aff9169a277 100644 --- a/usr.bin/tar/test/test_copy.c +++ b/usr.bin/tar/test/test_copy.c @@ -1,331 +1,337 @@ /*- * 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 LOOP_MAX 170 static void create_tree(void) { char buff[260]; char buff2[260]; int i; int fd; 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)); for (i = 0; i < LOOP_MAX; i++) { 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); assert(fd >= 0); assertEqualInt(i + 3, write(fd, buff, strlen(buff))); close(fd); /* 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. */ strcpy(buff2, buff); buff2[0] = 'm'; assertEqualInt(0, link(buff, buff2)); +#ifndef _WIN32 /* 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)); - +#else + skipping("create a symlink to the above"); +#endif /* Create a dir named "d/abcdef...". */ buff[0] = 'd'; assertEqualInt(0, mkdir(buff, 0775)); } chdir(".."); } #define LIMIT_NONE 0 #define LIMIT_USTAR 1 static void verify_tree(int limit) { struct stat st, st2; char filename[260]; char name1[260]; char name2[260]; char contents[260]; int i, j, r; int fd; int len; const char *p, *dp; DIR *d; struct dirent *de; /* Generate the names we know should be there and verify them. */ for (i = 1; i < LOOP_MAX; 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(name1, O_RDONLY); failure("Couldn't open \"%s\": %s", name1, strerror(errno)); if (assert(fd >= 0)) { len = read(fd, contents, i + 10); close(fd); assertEqualInt(len, i + 2); /* Verify contents of 'contents' */ contents[len] = '\0'; failure("Each test file contains its own name"); assertEqualString(name1, contents); /* stat() for dev/ino for next check */ assertEqualInt(0, lstat(name1, &st)); } } /* * ustar allows 100 chars for links, and we have * "original/" as part of the name, so the link * names here can't exceed 91 chars. */ 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..." */ 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); } } +#ifndef _WIN32 /* * Symlink text doesn't include the 'original/' prefix, * so the limit here is 100 characters. */ /* 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(name2 + 3, &st2))) { assert(S_ISLNK(st2.st_mode)); /* This is a symlink to the file above. */ 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); } } } - +#else + skipping("verify symlink"); +#endif /* 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)", name1, strlen(filename)); if (assertEqualInt(0, lstat(name1, &st2))) { if (assert(S_ISDIR(st2.st_mode))) { /* TODO: opendir/readdir this * directory and make sure * it's empty. */ } } } } /* Now make sure nothing is there that shouldn't be. */ for (dp = "dflms"; *dp != '\0'; ++dp) { char dir[2]; dir[0] = *dp; dir[1] = '\0'; d = opendir(dir); failure("Unable to open dir '%s'", dir); if (!assert(d != NULL)) continue; 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 < LOOP_MAX; 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); } } closedir(d); } } static void copy_basic(void) { int r; assertEqualInt(0, mkdir("plain", 0775)); assertEqualInt(0, chdir("plain")); /* * Use the tar program to create an archive. */ 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); /* Verify that nothing went to stdout or stderr. */ assertEmptyFile("pack.err"); assertEmptyFile("pack.out"); /* * Use tar to unpack the archive into another directory. */ r = systemf("%s xf archive >unpack.out 2>unpack.err", testprog); failure("Error invoking %s xf archive", testprog); assertEqualInt(r, 0); /* Verify that nothing went to stdout or stderr. */ assertEmptyFile("unpack.err"); assertEmptyFile("unpack.out"); verify_tree(LIMIT_NONE); assertEqualInt(0, chdir("..")); } static void copy_ustar(void) { const char *target = "ustar"; int r; assertEqualInt(0, mkdir(target, 0775)); assertEqualInt(0, chdir(target)); /* * Use the tar program to create an archive. */ 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. */ r = systemf("%s xf archive >unpack.out 2>unpack.err", testprog); failure("Error invoking %s xf archive", testprog); assertEqualInt(r, 0); /* Verify that nothing went to stdout or stderr. */ assertEmptyFile("unpack.err"); assertEmptyFile("unpack.out"); chdir("original"); verify_tree(LIMIT_USTAR); chdir("../.."); } DEFINE_TEST(test_copy) { int oldumask; oldumask = umask(0); create_tree(); /* Create sample files in "original" dir. */ /* Test simple "tar -c | tar -x" pipeline copy. */ copy_basic(); /* Same, but constrain to ustar format. */ copy_ustar(); umask(oldumask); } diff --git a/usr.bin/tar/test/test_option_T.c b/usr.bin/tar/test/test_option_T.c index 493dcc11dea5..9f4a2b34c6cf 100644 --- a/usr.bin/tar/test/test_option_T.c +++ b/usr.bin/tar/test/test_option_T.c @@ -1,145 +1,153 @@ /*- * Copyright (c) 2003-2008 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$"); static int touch(const char *fn) { int fd = open(fn, O_RDWR | O_CREAT, 0644); failure("Couldn't create file '%s', fd=%d, errno=%d (%s)\n", fn, fd, errno, strerror(errno)); if (!assert(fd > 0)) return (0); /* Failure. */ close(fd); return (1); /* Success */ } DEFINE_TEST(test_option_T) { FILE *f; int r; + struct stat st; /* Create a simple dir heirarchy; bail if anything fails. */ if (!assertEqualInt(0, mkdir("d1", 0755))) return; if (!assertEqualInt(0, mkdir("d1/d2", 0755))) return; if (!touch("d1/f1")) return; if (!touch("d1/f2")) return; if (!touch("d1/d2/f3")) return; if (!touch("d1/d2/f4")) return; if (!touch("d1/d2/f5")) return; /* Populate a file list */ f = fopen("filelist", "w+"); if (!assert(f != NULL)) return; fprintf(f, "d1/f1\n"); fprintf(f, "d1/d2/f4\n"); fclose(f); /* Populate a second file list */ f = fopen("filelist2", "w+"); if (!assert(f != NULL)) return; fprintf(f, "d1/d2/f3\n"); fprintf(f, "d1/d2/f5\n"); fclose(f); /* Use -c -T to archive up the files. */ r = systemf("%s -c -f test1.tar -T filelist > test1.out 2> test1.err", testprog); failure("Failure here probably means that tar can't archive zero-length files without reading them"); assert(r == 0); assertEmptyFile("test1.out"); assertEmptyFile("test1.err"); /* Use -x -T to dearchive the files */ if (!assertEqualInt(0, mkdir("test1", 0755))) return; systemf("%s -x -f test1.tar -T filelist -C test1" " > test1b.out 2> test1b.err", testprog); assertEmptyFile("test1b.out"); assertEmptyFile("test1b.err"); /* Verify the files were extracted. */ assertFileExists("test1/d1/f1"); assertFileNotExists("test1/d1/f2"); assertFileNotExists("test1/d1/d2/f3"); assertFileExists("test1/d1/d2/f4"); assertFileNotExists("test1/d1/d2/f5"); /* Use -r -T to add more files to the archive. */ systemf("%s -r -f test1.tar -T filelist2 > test2.out 2> test2.err", testprog); assertEmptyFile("test2.out"); assertEmptyFile("test2.err"); /* Use -x without -T to dearchive the files (ensure -r worked) */ if (!assertEqualInt(0, mkdir("test3", 0755))) return; systemf("%s -x -f test1.tar -C test3" " > test3.out 2> test3.err", testprog); assertEmptyFile("test3.out"); assertEmptyFile("test3.err"); /* Verify the files were extracted.*/ assertFileExists("test3/d1/f1"); assertFileNotExists("test3/d1/f2"); assertFileExists("test3/d1/d2/f3"); assertFileExists("test3/d1/d2/f4"); assertFileExists("test3/d1/d2/f5"); /* Use -x -T to dearchive the files (verify -x -T together) */ if (!assertEqualInt(0, mkdir("test2", 0755))) return; systemf("%s -x -f test1.tar -T filelist -C test2" " > test2b.out 2> test2b.err", testprog); assertEmptyFile("test2b.out"); assertEmptyFile("test2b.err"); /* Verify the files were extracted.*/ assertFileExists("test2/d1/f1"); assertFileNotExists("test2/d1/f2"); assertFileNotExists("test2/d1/d2/f3"); 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"); - + /* Does bsdtar support -s option ? */ + systemf("%s -cf - -s /foo/bar/ test4/d1/foo > NUL 2> check.err", + testprog); + assertEqualInt(0, stat("check.err", &st)); + if (st.st_size == 0) { + 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"); + } else { + skipping("bsdtar does not support -s option on this platform"); + } /* 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_option_s.c b/usr.bin/tar/test/test_option_s.c index b256e2aaa71f..1059066fd5cc 100644 --- a/usr.bin/tar/test/test_option_s.c +++ b/usr.bin/tar/test/test_option_s.c @@ -1,95 +1,106 @@ /*- * Copyright (c) 2003-2008 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$"); static int mkfile(const char *fn, const char *contents) { int fd = open(fn, O_RDWR | O_CREAT, 0644); failure("Couldn't create file '%s', fd=%d, errno=%d (%s)\n", fn, fd, errno, strerror(errno)); if (!assert(fd > 0)) return (1); /* Failure. */ if (contents != NULL) assertEqualInt(strlen(contents), write(fd, contents, strlen(contents))); assertEqualInt(0, close(fd)); return (0); /* Success */ } DEFINE_TEST(test_option_s) { + struct stat st; + /* Create a sample file heirarchy. */ assertEqualInt(0, mkdir("in", 0755)); assertEqualInt(0, mkdir("in/d1", 0755)); assertEqualInt(0, mkfile("in/d1/foo", "foo")); assertEqualInt(0, mkfile("in/d1/bar", "bar")); + /* Does bsdtar support -s option ? */ + systemf("%s -cf - -s /foo/bar/ in/d1/foo > NUL 2> check.err", + testprog); + assertEqualInt(0, stat("check.err", &st)); + if (st.st_size != 0) { + skipping("bsdtar does not support -s option on this platform"); + return; + } + /* * Test 1: Filename substitution when creating archives. */ assertEqualInt(0, mkdir("test1", 0755)); systemf("%s -cf - -s /foo/bar/ in/d1/foo | %s -xf - -C test1", testprog, testprog); assertFileContents("foo", 3, "test1/in/d1/bar"); systemf("%s -cf - -s /d1/d2/ in/d1/foo | %s -xf - -C test1", testprog, testprog); assertFileContents("foo", 3, "test1/in/d2/foo"); /* * Test 2: Basic substitution when extracting archive. */ assertEqualInt(0, mkdir("test2", 0755)); systemf("%s -cf - in/d1/foo | %s -xf - -s /foo/bar/ -C test2", testprog, testprog); assertFileContents("foo", 3, "test2/in/d1/bar"); /* * Test 3: Files with empty names shouldn't be archived. */ systemf("%s -cf - -s ,in/d1/foo,, in/d1/foo | %s -tvf - > in.lst", testprog, testprog); assertEmptyFile("in.lst"); /* * Test 4: Multiple substitutions when extracting archive. */ assertEqualInt(0, mkdir("test4", 0755)); systemf("%s -cf - in/d1/foo in/d1/bar | %s -xf - -s /foo/bar/ -s }bar}baz} -C test4", testprog, testprog); assertFileContents("foo", 3, "test4/in/d1/bar"); assertFileContents("bar", 3, "test4/in/d1/baz"); /* * Test 5: Name-switching substitutions when extracting archive. */ assertEqualInt(0, mkdir("test5", 0755)); systemf("%s -cf - in/d1/foo in/d1/bar | %s -xf - -s /foo/bar/ -s }bar}foo} -C test5", testprog, testprog); assertFileContents("foo", 3, "test5/in/d1/bar"); assertFileContents("bar", 3, "test5/in/d1/foo"); } diff --git a/usr.bin/tar/test/test_patterns.c b/usr.bin/tar/test/test_patterns.c index 281133aa4c00..36d4542450d4 100644 --- a/usr.bin/tar/test/test_patterns.c +++ b/usr.bin/tar/test/test_patterns.c @@ -1,100 +1,104 @@ /*- * 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; - const char *reffile2 = "test_patterns_2.tgz"; - const char *reffile3 = "test_patterns_3.tgz"; + const char *reffile2 = "test_patterns_2.tar"; + const char *reffile3 = "test_patterns_3.tar"; const char *p; /* * Test basic command-line pattern handling. */ /* * Test 1: Files on the command line that don't get matched * didn't produce an error. * * 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); + r = systemf("%s cfv 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); + r = systemf("%s xfv 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); /* * Test 2: Check basic matching of full paths that start with / */ extract_reference_file(reffile2); r = systemf("%s tf %s /tmp/foo/bar > tar2a.out 2> tar2a.err", testprog, reffile2); assertEqualInt(r, 0); +#ifndef _WIN32 p = "/tmp/foo/bar/\n/tmp/foo/bar/baz\n"; +#else + p = "/tmp/foo/bar/\r\n/tmp/foo/bar/baz\r\n"; +#endif assertFileContents(p, strlen(p), "tar2a.out"); assertEmptyFile("tar2a.err"); /* * Test 3 archive has some entries starting with '/' and some not. */ extract_reference_file(reffile3); /* Test 3a: Pattern tmp/foo/bar should not match /tmp/foo/bar */ r = systemf("%s xf %s tmp/foo/bar > tar3a.out 2> tar3a.err", testprog, reffile3); assert(r != 0); assertEmptyFile("tar3a.out"); /* Test 3b: Pattern /tmp/foo/baz should not match tmp/foo/baz */ assertNonEmptyFile("tar3a.err"); /* Again, with the '/' */ r = systemf("%s xf %s /tmp/foo/baz > tar3b.out 2> tar3b.err", testprog, reffile3); assert(r != 0); assertEmptyFile("tar3b.out"); assertNonEmptyFile("tar3b.err"); /* Test 3c: ./tmp/foo/bar should not match /tmp/foo/bar */ r = systemf("%s xf %s ./tmp/foo/bar > tar3c.out 2> tar3c.err", testprog, reffile3); assert(r != 0); assertEmptyFile("tar3c.out"); assertNonEmptyFile("tar3c.err"); /* Test 3d: ./tmp/foo/baz should match tmp/foo/baz */ r = systemf("%s xf %s ./tmp/foo/baz > tar3d.out 2> tar3d.err", testprog, reffile3); assertEqualInt(r, 0); assertEmptyFile("tar3d.out"); assertEmptyFile("tar3d.err"); assertEqualInt(0, access("tmp/foo/baz/bar", F_OK)); } diff --git a/usr.bin/tar/test/test_patterns_2.tar.uu b/usr.bin/tar/test/test_patterns_2.tar.uu new file mode 100644 index 000000000000..1ed9a7d1aea0 --- /dev/null +++ b/usr.bin/tar/test/test_patterns_2.tar.uu @@ -0,0 +1,232 @@ +$FreeBSD$ +begin 644 test_patterns_2.tar +M+W1M<"]F;V\O```````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````#`P,#@`````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````P +M,#`V-#0@`#`P,3@`````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````#`P,#8T-"``,#`Q-S4P(``P,#`P,#`@`#`P,#`P +M,#`P,#`P(#$Q,#4Q,C$R-C4S(#`Q,S8V-P`@,``````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````````!U.:W8X?V;/==9XM\1*0*P:2J59"QCN8ZO:A*4*Z`]OR?\Y_C!7Y`P``````````````?.,! -(*>E$>P`H```` -` -end diff --git a/usr.bin/tar/test/test_patterns_3.tar.uu b/usr.bin/tar/test/test_patterns_3.tar.uu new file mode 100644 index 000000000000..e8d487ed259b --- /dev/null +++ b/usr.bin/tar/test/test_patterns_3.tar.uu @@ -0,0 +1,232 @@ +$FreeBSD$ +begin 644 test_patterns_3.tar +M+W1M<"]F;V\O8F%R+P`````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````#`P,#65]>2CUUQX+TO/\F_@H<0_BWG^I;Z`#?D'X?T#``````````````!\Y0FE&!YR`"@````` -` -end diff --git a/usr.bin/tar/test/test_strip_components.c b/usr.bin/tar/test/test_strip_components.c index 0f98dfc08632..6fc4b301c520 100644 --- a/usr.bin/tar/test/test_strip_components.c +++ b/usr.bin/tar/test/test_strip_components.c @@ -1,106 +1,110 @@ /*- * 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$"); static int touch(const char *fn) { int fd = open(fn, O_RDWR | O_CREAT, 0644); failure("Couldn't create file '%s', fd=%d, errno=%d (%s)\n", fn, fd, errno, strerror(errno)); if (!assert(fd > 0)) return (0); /* Failure. */ close(fd); return (1); /* Success */ } DEFINE_TEST(test_strip_components) { struct stat st; assertEqualInt(0, mkdir("d0", 0755)); assertEqualInt(0, chdir("d0")); assertEqualInt(0, mkdir("d1", 0755)); assertEqualInt(0, mkdir("d1/d2", 0755)); assertEqualInt(0, mkdir("d1/d2/d3", 0755)); assertEqualInt(1, touch("d1/d2/f1")); assertEqualInt(0, link("d1/d2/f1", "l1")); assertEqualInt(0, link("d1/d2/f1", "d1/l2")); assertEqualInt(0, symlink("d1/d2/f1", "s1")); assertEqualInt(0, symlink("d2/f1", "d1/s2")); assertEqualInt(0, chdir("..")); assertEqualInt(0, systemf("%s -cf test.tar d0", testprog)); assertEqualInt(0, mkdir("target", 0755)); assertEqualInt(0, systemf("%s -x -C target --strip-components 2 " "-f test.tar", testprog)); failure("d0/ is too short and should not get restored"); assertEqualInt(-1, lstat("target/d0", &st)); failure("d0/d1/ is too short and should not get restored"); assertEqualInt(-1, lstat("target/d1", &st)); failure("d0/d1/s2 is a symlink to something that won't be extracted"); +#ifndef _WIN32 assertEqualInt(-1, stat("target/s2", &st)); +#else + skipping("symlink with stat()"); +#endif assertEqualInt(0, lstat("target/s2", &st)); failure("d0/d1/d2 should be extracted"); assertEqualInt(0, lstat("target/d2", &st)); /* * This next is a complicated case. d0/l1, d0/d1/l2, and * d0/d1/d2/f1 are all hardlinks to the same file; d0/l1 can't * be extracted with --strip-components=2 and the other two * can. Remember that tar normally stores the first file with * a body and the other as hardlink entries to the first * appearance. So the final result depends on the order in * which these three names get archived. If d0/l1 is first, * none of the three can be restored. If either of the longer * names are first, then the two longer ones can both be * restored. * * The tree-walking code used by bsdtar always visits files * before subdirectories, so bsdtar's behavior is fortunately * deterministic: d0/l1 will always get stored first and the * other two will be stored as hardlinks to d0/l1. Since * d0/l1 can't be extracted, none of these three will be * extracted. * * It may be worth extending this test to force a particular * archiving order so as to exercise both of the cases described * above. * * Of course, this is all totally different for cpio and newc * formats because the hardlink management is different. * TODO: Rename this to test_strip_components_tar and create * parallel tests for cpio and newc formats. */ failure("d0/l1 is too short and should not get restored"); assertEqualInt(-1, lstat("target/l1", &st)); failure("d0/d1/l2 is a hardlink to file whose name was too short"); assertEqualInt(-1, lstat("target/l2", &st)); failure("d0/d1/d2/f1 is a hardlink to file whose name was too short"); assertEqualInt(-1, lstat("target/d2/f1", &st)); } diff --git a/usr.bin/tar/test/test_symlink_dir.c b/usr.bin/tar/test/test_symlink_dir.c index e256c940bb9e..d5c493db28e2 100644 --- a/usr.bin/tar/test/test_symlink_dir.c +++ b/usr.bin/tar/test/test_symlink_dir.c @@ -1,172 +1,193 @@ /*- * 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$"); /* * tar -x -P should follow existing symlinks for dirs, but not other * content. Plain tar -x should remove symlinks when they're in the * way of a dir extraction. */ static int mkfile(const char *name, int mode, const char *contents, ssize_t size) { int fd = open(name, O_CREAT | O_WRONLY, mode); if (fd < 0) return (-1); if (size != write(fd, contents, size)) { close(fd); return (-1); } close(fd); return (0); } DEFINE_TEST(test_symlink_dir) { - struct stat st, st2; + struct stat st; +#ifndef _WIN32 + struct stat st2; +#endif int oldumask; oldumask = umask(0); assertEqualInt(0, mkdir("source", 0755)); assertEqualInt(0, mkfile("source/file", 0755, "a", 1)); assertEqualInt(0, mkfile("source/file2", 0755, "ab", 2)); assertEqualInt(0, mkdir("source/dir", 0755)); assertEqualInt(0, mkdir("source/dir/d", 0755)); assertEqualInt(0, mkfile("source/dir/f", 0755, "abc", 3)); assertEqualInt(0, mkdir("source/dir2", 0755)); assertEqualInt(0, mkdir("source/dir2/d2", 0755)); assertEqualInt(0, mkfile("source/dir2/f2", 0755, "abcd", 4)); assertEqualInt(0, mkdir("source/dir3", 0755)); assertEqualInt(0, mkdir("source/dir3/d3", 0755)); assertEqualInt(0, mkfile("source/dir3/f3", 0755, "abcde", 5)); assertEqualInt(0, systemf("%s -cf test.tar -C source dir dir2 dir3 file file2", testprog)); /* * Extract with -x and without -P. */ assertEqualInt(0, mkdir("dest1", 0755)); /* "dir" is a symlink to an existing "real_dir" */ assertEqualInt(0, mkdir("dest1/real_dir", 0755)); +#ifndef _WIN32 assertEqualInt(0, symlink("real_dir", "dest1/dir")); /* "dir2" is a symlink to a non-existing "real_dir2" */ assertEqualInt(0, symlink("real_dir2", "dest1/dir2")); +#else + skipping("symlink does not work on this platform"); +#endif /* "dir3" is a symlink to an existing "non_dir3" */ assertEqualInt(0, mkfile("dest1/non_dir3", 0755, "abcdef", 6)); assertEqualInt(0, symlink("non_dir3", "dest1/dir3")); /* "file" is a symlink to existing "real_file" */ assertEqualInt(0, mkfile("dest1/real_file", 0755, "abcdefg", 7)); assertEqualInt(0, symlink("real_file", "dest1/file")); +#ifndef _WIN32 /* "file2" is a symlink to non-existing "real_file2" */ assertEqualInt(0, symlink("real_file2", "dest1/file2")); - +#else + skipping("symlink does not work on this platform"); +#endif assertEqualInt(0, systemf("%s -xf test.tar -C dest1", testprog)); /* dest1/dir symlink should be removed */ assertEqualInt(0, lstat("dest1/dir", &st)); failure("symlink to dir was followed when it shouldn't be"); assert(S_ISDIR(st.st_mode)); /* dest1/dir2 symlink should be removed */ assertEqualInt(0, lstat("dest1/dir2", &st)); failure("Broken symlink wasn't replaced with dir"); assert(S_ISDIR(st.st_mode)); /* dest1/dir3 symlink should be removed */ assertEqualInt(0, lstat("dest1/dir3", &st)); failure("Symlink to non-dir wasn't replaced with dir"); assert(S_ISDIR(st.st_mode)); /* dest1/file symlink should be removed */ assertEqualInt(0, lstat("dest1/file", &st)); failure("Symlink to existing file should be removed"); assert(S_ISREG(st.st_mode)); /* dest1/file2 symlink should be removed */ assertEqualInt(0, lstat("dest1/file2", &st)); failure("Symlink to non-existing file should be removed"); assert(S_ISREG(st.st_mode)); /* * Extract with both -x and -P */ assertEqualInt(0, mkdir("dest2", 0755)); /* "dir" is a symlink to existing "real_dir" */ assertEqualInt(0, mkdir("dest2/real_dir", 0755)); +#ifndef _WIN32 assertEqualInt(0, symlink("real_dir", "dest2/dir")); /* "dir2" is a symlink to a non-existing "real_dir2" */ assertEqualInt(0, symlink("real_dir2", "dest2/dir2")); +#else + skipping("symlink does not work on this platform"); +#endif /* "dir3" is a symlink to an existing "non_dir3" */ assertEqualInt(0, mkfile("dest2/non_dir3", 0755, "abcdefgh", 8)); assertEqualInt(0, symlink("non_dir3", "dest2/dir3")); /* "file" is a symlink to existing "real_file" */ assertEqualInt(0, mkfile("dest2/real_file", 0755, "abcdefghi", 9)); assertEqualInt(0, symlink("real_file", "dest2/file")); +#ifndef _WIN32 /* "file2" is a symlink to non-existing "real_file2" */ assertEqualInt(0, symlink("real_file2", "dest2/file2")); - +#else + skipping("symlink does not work on this platform"); +#endif assertEqualInt(0, systemf("%s -xPf test.tar -C dest2", testprog)); /* dest2/dir symlink should be followed */ assertEqualInt(0, lstat("dest2/dir", &st)); failure("tar -xP removed symlink instead of following it"); +#ifndef _WIN32 if (assert(S_ISLNK(st.st_mode))) { /* Only verify what the symlink points to if it * really is a symlink. */ failure("The symlink should point to a directory"); assertEqualInt(0, stat("dest2/dir", &st)); assert(S_ISDIR(st.st_mode)); failure("The pre-existing directory should still be there"); assertEqualInt(0, lstat("dest2/real_dir", &st2)); assert(S_ISDIR(st2.st_mode)); assertEqualInt(st.st_dev, st2.st_dev); failure("symlink should still point to the existing directory"); assertEqualInt(st.st_ino, st2.st_ino); } +#else + skipping("symlink does not work on this platform"); +#endif /* Contents of 'dir' should be restored */ assertEqualInt(0, lstat("dest2/dir/d", &st)); assert(S_ISDIR(st.st_mode)); assertEqualInt(0, lstat("dest2/dir/f", &st)); assert(S_ISREG(st.st_mode)); assertEqualInt(3, st.st_size); /* dest2/dir2 symlink should be removed */ assertEqualInt(0, lstat("dest2/dir2", &st)); failure("Broken symlink wasn't replaced with dir"); assert(S_ISDIR(st.st_mode)); /* dest2/dir3 symlink should be removed */ assertEqualInt(0, lstat("dest2/dir3", &st)); failure("Symlink to non-dir wasn't replaced with dir"); assert(S_ISDIR(st.st_mode)); /* dest2/file symlink should be removed; * even -P shouldn't follow symlinks for files */ assertEqualInt(0, lstat("dest2/file", &st)); failure("Symlink to existing file should be removed"); assert(S_ISREG(st.st_mode)); /* dest2/file2 symlink should be removed */ assertEqualInt(0, lstat("dest2/file2", &st)); failure("Symlink to non-existing file should be removed"); assert(S_ISREG(st.st_mode)); } diff --git a/usr.bin/tar/test/test_version.c b/usr.bin/tar/test/test_version.c index 6f2f6a0b5a0c..4f249a517b22 100644 --- a/usr.bin/tar/test/test_version.c +++ b/usr.bin/tar/test/test_version.c @@ -1,93 +1,96 @@ /*- * 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$"); /* * Test that --version option works and generates reasonable output. */ DEFINE_TEST(test_version) { int r; char *p, *q; size_t s; r = systemf("%s --version >version.stdout 2>version.stderr", testprog); if (r != 0) r = systemf("%s -W version >version.stdout 2>version.stderr", testprog); failure("Unable to run either %s --version or %s -W version", testprog, testprog); if (!assert(r == 0)) return; /* --version should generate nothing to stdout. */ assertEmptyFile("version.stderr"); /* Verify format of version message. */ q = p = slurpfile(&s, "version.stdout"); /* Version message should start with name of program, then space. */ assert(s > 6); - failure("Version: %s", p); + failure("Version must start with 'bsdtar': ``%s''", p); assertEqualMem(q, "bsdtar ", 7); q += 7; s -= 7; /* Version number is a series of digits and periods. */ while (s > 0 && (*q == '.' || (*q >= '0' && *q <= '9'))) { ++q; --s; } /* Version number terminated by space. */ - failure("Version: %s", p); + failure("No space after bsdtar version: ``%s''", p); assert(s > 1); /* Skip a single trailing a,b,c, or d. */ if (*q == 'a' || *q == 'b' || *q == 'c' || *q == 'd') ++q; - failure("Version: %s", p); + failure("No space after bsdtar version: ``%s''", p); assert(*q == ' '); ++q; --s; /* Separator. */ - failure("Version: %s", p); + failure("No `-' between bsdtar and libarchive versions: ``%s''", p); assertEqualMem(q, "- ", 2); q += 2; s -= 2; /* libarchive name and version number */ - failure("Version: %s", p); + failure("Not long enough for libarchive version: ``%s''", p); assert(s > 11); - failure("Version: %s", p); + failure("Libarchive version must start with `libarchive': ``%s''", p); assertEqualMem(q, "libarchive ", 11); q += 11; s -= 11; /* Version number is a series of digits and periods. */ while (s > 0 && (*q == '.' || (*q >= '0' && *q <= '9'))) { ++q; --s; } /* Skip a single trailing a,b,c, or d. */ if (*q == 'a' || *q == 'b' || *q == 'c' || *q == 'd') ++q; - /* All terminated by a newline. */ + /* All terminated by end-of-line. */ assert(s >= 1); + /* Skip an optional CR character (e.g., Windows) */ + failure("Version output must end with \\n or \\r\\n"); + if (*q == '\r') { ++q; --s; } assertEqualMem(q, "\n", 1); free(p); } diff --git a/usr.bin/tar/tree.c b/usr.bin/tar/tree.c index de2e9ef7b32e..f47330b2c97f 100644 --- a/usr.bin/tar/tree.c +++ b/usr.bin/tar/tree.c @@ -1,563 +1,596 @@ /*- * 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. */ /*- * This is a new directory-walking system that addresses a number * of problems I've had with fts(3). In particular, it has no * pathname-length limits (other than the size of 'int'), handles * deep logical traversals, uses considerably less memory, and has * an opaque interface (easier to modify in the future). * * Internally, it keeps a single list of "tree_entry" items that * represent filesystem objects that require further attention. * Non-directories are not kept in memory: they are pulled from * readdir(), returned to the client, then freed as soon as possible. * Any directory entry to be traversed gets pushed onto the stack. * * There is surprisingly little information that needs to be kept for * each item on the stack. Just the name, depth (represented here as the * string length of the parent directory's pathname), and some markers * indicating how to get back to the parent (via chdir("..") for a * regular dir or via fchdir(2) for a symlink). */ #include "bsdtar_platform.h" __FBSDID("$FreeBSD$"); #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_DIRENT_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #include "tree.h" /* * TODO: * 1) Loop checking. * 3) Arbitrary logical traversals by closing/reopening intermediate fds. */ struct tree_entry { struct tree_entry *next; struct tree_entry *parent; char *name; size_t dirname_length; dev_t dev; ino_t ino; int fd; +#ifdef _WIN32 + char *fullpath; +#endif int flags; }; /* Definitions for tree_entry.flags bitmap. */ #define isDir 1 /* This entry is a regular directory. */ #define isDirLink 2 /* This entry is a symbolic link to a directory. */ #define needsPreVisit 4 /* This entry needs to be previsited. */ #define needsPostVisit 8 /* This entry needs to be postvisited. */ /* * Local data for this package. */ struct tree { struct tree_entry *stack; struct tree_entry *current; DIR *d; int initialDirFd; +#ifdef _WIN32 + char *initialDir; +#endif int flags; int visit_type; int tree_errno; /* Error code from last failed operation. */ char *buff; const char *basename; size_t buff_length; size_t path_length; size_t dirname_length; int depth; int openCount; int maxOpenCount; struct stat lst; struct stat st; }; /* Definitions for tree.flags bitmap. */ #define needsReturn 8 /* Marks first entry as not having been returned yet. */ #define hasStat 16 /* The st entry is set. */ #define hasLstat 32 /* The lst entry is set. */ #ifdef HAVE_DIRENT_D_NAMLEN /* BSD extension; avoids need for a strlen() call. */ #define D_NAMELEN(dp) (dp)->d_namlen #else #define D_NAMELEN(dp) (strlen((dp)->d_name)) #endif #if 0 #include void tree_dump(struct tree *t, FILE *out) { struct tree_entry *te; fprintf(out, "\tdepth: %d\n", t->depth); fprintf(out, "\tbuff: %s\n", t->buff); fprintf(out, "\tpwd: "); fflush(stdout); system("pwd"); fprintf(out, "\taccess: %s\n", t->basename); fprintf(out, "\tstack:\n"); for (te = t->stack; te != NULL; te = te->next) { fprintf(out, "\t\tte->name: %s%s%s\n", te->name, te->flags & needsPreVisit ? "" : " *", t->current == te ? " (current)" : ""); } } #endif /* * Add a directory path to the current stack. */ static void tree_push(struct tree *t, const char *path) { struct tree_entry *te; te = malloc(sizeof(*te)); memset(te, 0, sizeof(*te)); te->next = t->stack; t->stack = te; te->fd = -1; +#ifdef _WIN32 + te->fullpath = NULL; +#endif te->name = strdup(path); te->flags = needsPreVisit | needsPostVisit; te->dirname_length = t->dirname_length; } /* * Append a name to the current path. */ static void tree_append(struct tree *t, const char *name, size_t name_length) { char *p; if (t->buff != NULL) t->buff[t->dirname_length] = '\0'; /* Strip trailing '/' from name, unless entire name is "/". */ while (name_length > 1 && name[name_length - 1] == '/') name_length--; /* Resize pathname buffer as needed. */ while (name_length + 1 + t->dirname_length >= t->buff_length) { t->buff_length *= 2; if (t->buff_length < 1024) t->buff_length = 1024; t->buff = realloc(t->buff, t->buff_length); } p = t->buff + t->dirname_length; t->path_length = t->dirname_length + name_length; /* Add a separating '/' if it's needed. */ if (t->dirname_length > 0 && p[-1] != '/') { *p++ = '/'; t->path_length ++; } strncpy(p, name, name_length); p[name_length] = '\0'; t->basename = p; } /* * Open a directory tree for traversal. */ struct tree * tree_open(const char *path) { struct tree *t; t = malloc(sizeof(*t)); memset(t, 0, sizeof(*t)); tree_append(t, path, strlen(path)); t->initialDirFd = open(".", O_RDONLY); +#ifdef _WIN32 + if (t->initialDirFd >= 0) + t->initialDir = getcwd(NULL, 0); +#endif /* * During most of the traversal, items are set up and then * returned immediately from tree_next(). That doesn't work * for the very first entry, so we set a flag for this special * case. */ t->flags = needsReturn; return (t); } /* * We've finished a directory; ascend back to the parent. */ static int tree_ascend(struct tree *t) { struct tree_entry *te; int r = 0; te = t->stack; t->depth--; if (te->flags & isDirLink) { +#ifdef HAVE_FCHDIR if (fchdir(te->fd) != 0) { t->tree_errno = errno; r = TREE_ERROR_FATAL; } +#endif +#ifdef _WIN32 + if (te->fullpath != NULL && chdir(te->fullpath) != 0) { + t->tree_errno = errno; + r = TREE_ERROR_FATAL; + } + free(te->fullpath); + te->fullpath = NULL; +#endif close(te->fd); t->openCount--; } else { if (chdir("..") != 0) { t->tree_errno = errno; r = TREE_ERROR_FATAL; } } return (r); } /* * Pop the working stack. */ static void tree_pop(struct tree *t) { struct tree_entry *te; t->buff[t->dirname_length] = '\0'; if (t->stack == t->current && t->current != NULL) t->current = t->current->parent; te = t->stack; t->stack = te->next; t->dirname_length = te->dirname_length; t->basename = t->buff + t->dirname_length; /* Special case: starting dir doesn't skip leading '/'. */ if (t->dirname_length > 0) t->basename++; free(te->name); free(te); } /* * Get the next item in the tree traversal. */ int tree_next(struct tree *t) { struct dirent *de = NULL; int r; /* If we're called again after a fatal error, that's an API * violation. Just crash now. */ if (t->visit_type == TREE_ERROR_FATAL) { const char *msg = "Unable to continue traversing" " directory heirarchy after a fatal error."; write(2, msg, strlen(msg)); *(int *)0 = 1; /* Deliberate SEGV; NULL pointer dereference. */ exit(1); /* In case the SEGV didn't work. */ } /* Handle the startup case by returning the initial entry. */ if (t->flags & needsReturn) { t->flags &= ~needsReturn; return (t->visit_type = TREE_REGULAR); } while (t->stack != NULL) { /* If there's an open dir, get the next entry from there. */ while (t->d != NULL) { de = readdir(t->d); if (de == NULL) { closedir(t->d); t->d = NULL; } else if (de->d_name[0] == '.' && de->d_name[1] == '\0') { /* Skip '.' */ } else if (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0') { /* Skip '..' */ } else { /* * Append the path to the current path * and return it. */ tree_append(t, de->d_name, D_NAMELEN(de)); t->flags &= ~hasLstat; t->flags &= ~hasStat; return (t->visit_type = TREE_REGULAR); } } /* If the current dir needs to be visited, set it up. */ if (t->stack->flags & needsPreVisit) { t->current = t->stack; tree_append(t, t->stack->name, strlen(t->stack->name)); t->stack->flags &= ~needsPreVisit; /* If it is a link, set up fd for the ascent. */ if (t->stack->flags & isDirLink) { t->stack->fd = open(".", O_RDONLY); +#ifdef _WIN32 + t->stack->fullpath = getcwd(NULL, 0); +#endif t->openCount++; if (t->openCount > t->maxOpenCount) t->maxOpenCount = t->openCount; } t->dirname_length = t->path_length; if (chdir(t->stack->name) != 0) { /* chdir() failed; return error */ tree_pop(t); t->tree_errno = errno; return (t->visit_type = TREE_ERROR_DIR); } t->depth++; t->d = opendir("."); if (t->d == NULL) { r = tree_ascend(t); /* Undo "chdir" */ tree_pop(t); t->tree_errno = errno; t->visit_type = r != 0 ? r : TREE_ERROR_DIR; return (t->visit_type); } t->flags &= ~hasLstat; t->flags &= ~hasStat; t->basename = "."; return (t->visit_type = TREE_POSTDESCENT); } /* We've done everything necessary for the top stack entry. */ if (t->stack->flags & needsPostVisit) { r = tree_ascend(t); tree_pop(t); t->flags &= ~hasLstat; t->flags &= ~hasStat; t->visit_type = r != 0 ? r : TREE_POSTASCENT; return (t->visit_type); } } return (t->visit_type = 0); } /* * Return error code. */ int tree_errno(struct tree *t) { return (t->tree_errno); } /* * Called by the client to mark the directory just returned from * tree_next() as needing to be visited. */ void tree_descend(struct tree *t) { if (t->visit_type != TREE_REGULAR) return; if (tree_current_is_physical_dir(t)) { tree_push(t, t->basename); t->stack->flags |= isDir; } else if (tree_current_is_dir(t)) { tree_push(t, t->basename); t->stack->flags |= isDirLink; } } /* * Get the stat() data for the entry just returned from tree_next(). */ const struct stat * tree_current_stat(struct tree *t) { if (!(t->flags & hasStat)) { if (stat(t->basename, &t->st) != 0) return NULL; t->flags |= hasStat; } return (&t->st); } /* * Get the lstat() data for the entry just returned from tree_next(). */ const struct stat * tree_current_lstat(struct tree *t) { if (!(t->flags & hasLstat)) { if (lstat(t->basename, &t->lst) != 0) return NULL; t->flags |= hasLstat; } return (&t->lst); } /* * Test whether current entry is a dir or link to a dir. */ int tree_current_is_dir(struct tree *t) { const struct stat *st; /* * If we already have lstat() info, then try some * cheap tests to determine if this is a dir. */ if (t->flags & hasLstat) { /* If lstat() says it's a dir, it must be a dir. */ if (S_ISDIR(tree_current_lstat(t)->st_mode)) return 1; /* Not a dir; might be a link to a dir. */ /* If it's not a link, then it's not a link to a dir. */ if (!S_ISLNK(tree_current_lstat(t)->st_mode)) return 0; /* * It's a link, but we don't know what it's a link to, * so we'll have to use stat(). */ } st = tree_current_stat(t); /* If we can't stat it, it's not a dir. */ if (st == NULL) return 0; /* Use the definitive test. Hopefully this is cached. */ return (S_ISDIR(st->st_mode)); } /* * Test whether current entry is a physical directory. Usually, we * already have at least one of stat() or lstat() in memory, so we * use tricks to try to avoid an extra trip to the disk. */ int tree_current_is_physical_dir(struct tree *t) { const struct stat *st; /* * If stat() says it isn't a dir, then it's not a dir. * If stat() data is cached, this check is free, so do it first. */ if ((t->flags & hasStat) && (!S_ISDIR(tree_current_stat(t)->st_mode))) return 0; /* * Either stat() said it was a dir (in which case, we have * to determine whether it's really a link to a dir) or * stat() info wasn't available. So we use lstat(), which * hopefully is already cached. */ st = tree_current_lstat(t); /* If we can't stat it, it's not a dir. */ if (st == NULL) return 0; /* Use the definitive test. Hopefully this is cached. */ return (S_ISDIR(st->st_mode)); } /* * Test whether current entry is a symbolic link. */ int tree_current_is_physical_link(struct tree *t) { const struct stat *st = tree_current_lstat(t); if (st == NULL) return 0; return (S_ISLNK(st->st_mode)); } /* * Return the access path for the entry just returned from tree_next(). */ const char * tree_current_access_path(struct tree *t) { return (t->basename); } /* * Return the full path for the entry just returned from tree_next(). */ const char * tree_current_path(struct tree *t) { return (t->buff); } /* * Return the length of the path for the entry just returned from tree_next(). */ size_t tree_current_pathlen(struct tree *t) { return (t->path_length); } /* * Return the nesting depth of the entry just returned from tree_next(). */ int tree_current_depth(struct tree *t) { return (t->depth); } /* * Terminate the traversal and release any resources. */ void tree_close(struct tree *t) { /* Release anything remaining in the stack. */ while (t->stack != NULL) tree_pop(t); if (t->buff) free(t->buff); /* chdir() back to where we started. */ if (t->initialDirFd >= 0) { +#ifdef HAVE_FCHDIR fchdir(t->initialDirFd); +#endif +#ifdef _WIN32 + chdir(t->initialDir); + free(t->initialDir); + t->initialDir = NULL; +#endif close(t->initialDirFd); t->initialDirFd = -1; } free(t); } diff --git a/usr.bin/tar/util.c b/usr.bin/tar/util.c index 367409b321f8..442a95c99c49 100644 --- a/usr.bin/tar/util.c +++ b/usr.bin/tar/util.c @@ -1,580 +1,603 @@ /*- * 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 "bsdtar_platform.h" __FBSDID("$FreeBSD$"); #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_TYPES_H #include /* Linux doesn't define mode_t, etc. in sys/stat.h. */ #endif #include #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_STDARG_H #include #endif #include #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_WCTYPE_H #include #else /* If we don't have wctype, we need to hack up some version of iswprint(). */ #define iswprint isprint #endif #include "bsdtar.h" static void bsdtar_vwarnc(struct bsdtar *, int code, const char *fmt, va_list ap); static size_t bsdtar_expand_char(char *, size_t, char); static const char *strip_components(const char *path, int elements); /* TODO: Hack up a version of mbtowc for platforms with no wide * character support at all. I think the following might suffice, * but it needs careful testing. * #if !HAVE_MBTOWC * #define mbtowc(wcp, p, n) ((*wcp = *p), 1) * #endif */ /* * Print a string, taking care with any non-printable characters. * * Note that we use a stack-allocated buffer to receive the formatted * string if we can. This is partly performance (avoiding a call to * malloc()), partly out of expedience (we have to call vsnprintf() * before malloc() anyway to find out how big a buffer we need; we may * as well point that first call at a small local buffer in case it * works), but mostly for safety (so we can use this to print messages * about out-of-memory conditions). */ void safe_fprintf(FILE *f, const char *fmt, ...) { char fmtbuff_stack[256]; /* Place to format the printf() string. */ char outbuff[256]; /* Buffer for outgoing characters. */ char *fmtbuff_heap; /* If fmtbuff_stack is too small, we use malloc */ char *fmtbuff; /* Pointer to fmtbuff_stack or fmtbuff_heap. */ int fmtbuff_length; int length; va_list ap; const char *p; unsigned i; wchar_t wc; char try_wc; /* Use a stack-allocated buffer if we can, for speed and safety. */ fmtbuff_heap = NULL; fmtbuff_length = sizeof(fmtbuff_stack); fmtbuff = fmtbuff_stack; /* Try formatting into the stack buffer. */ va_start(ap, fmt); length = vsnprintf(fmtbuff, fmtbuff_length, fmt, ap); va_end(ap); /* If the result was too large, allocate a buffer on the heap. */ if (length >= fmtbuff_length) { fmtbuff_length = length+1; fmtbuff_heap = malloc(fmtbuff_length); /* Reformat the result into the heap buffer if we can. */ if (fmtbuff_heap != NULL) { fmtbuff = fmtbuff_heap; va_start(ap, fmt); length = vsnprintf(fmtbuff, fmtbuff_length, fmt, ap); va_end(ap); } else { /* Leave fmtbuff pointing to the truncated * string in fmtbuff_stack. */ length = sizeof(fmtbuff_stack) - 1; } } /* Note: mbrtowc() has a cleaner API, but mbtowc() seems a bit * more portable, so we use that here instead. */ mbtowc(NULL, NULL, 0); /* Reset the shift state. */ /* Write data, expanding unprintable characters. */ p = fmtbuff; i = 0; try_wc = 1; while (*p != '\0') { int n; /* Convert to wide char, test if the wide * char is printable in the current locale. */ if (try_wc && (n = mbtowc(&wc, p, length)) != -1) { length -= n; if (iswprint(wc) && wc != L'\\') { /* Printable, copy the bytes through. */ while (n-- > 0) outbuff[i++] = *p++; } else { /* Not printable, format the bytes. */ while (n-- > 0) i += bsdtar_expand_char( outbuff, i, *p++); } } else { /* After any conversion failure, don't bother * trying to convert the rest. */ i += bsdtar_expand_char(outbuff, i, *p++); try_wc = 0; } /* If our output buffer is full, dump it and keep going. */ if (i > (sizeof(outbuff) - 20)) { outbuff[i++] = '\0'; fprintf(f, "%s", outbuff); i = 0; } } outbuff[i++] = '\0'; fprintf(f, "%s", outbuff); /* If we allocated a heap-based formatting buffer, free it now. */ if (fmtbuff_heap != NULL) free(fmtbuff_heap); } /* * Render an arbitrary sequence of bytes into printable ASCII characters. */ static size_t bsdtar_expand_char(char *buff, size_t offset, char c) { size_t i = offset; if (isprint(c) && c != '\\') buff[i++] = c; else { buff[i++] = '\\'; switch (c) { case '\a': buff[i++] = 'a'; break; case '\b': buff[i++] = 'b'; break; case '\f': buff[i++] = 'f'; break; case '\n': buff[i++] = 'n'; break; #if '\r' != '\n' /* On some platforms, \n and \r are the same. */ case '\r': buff[i++] = 'r'; break; #endif case '\t': buff[i++] = 't'; break; case '\v': buff[i++] = 'v'; break; case '\\': buff[i++] = '\\'; break; default: sprintf(buff + i, "%03o", 0xFF & (int)c); i += 3; } } return (i - offset); } static void bsdtar_vwarnc(struct bsdtar *bsdtar, int code, const char *fmt, va_list ap) { fprintf(stderr, "%s: ", bsdtar->progname); vfprintf(stderr, fmt, ap); if (code != 0) fprintf(stderr, ": %s", strerror(code)); fprintf(stderr, "\n"); } void bsdtar_warnc(struct bsdtar *bsdtar, int code, const char *fmt, ...) { va_list ap; va_start(ap, fmt); bsdtar_vwarnc(bsdtar, code, fmt, ap); va_end(ap); } void bsdtar_errc(struct bsdtar *bsdtar, int eval, int code, const char *fmt, ...) { va_list ap; va_start(ap, fmt); bsdtar_vwarnc(bsdtar, code, fmt, ap); va_end(ap); exit(eval); } int yes(const char *fmt, ...) { char buff[32]; char *p; ssize_t l; va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, " (y/N)? "); fflush(stderr); l = read(2, buff, sizeof(buff) - 1); if (l <= 0) return (0); buff[l] = 0; for (p = buff; *p != '\0'; p++) { if (isspace(0xff & (int)*p)) continue; switch(*p) { case 'y': case 'Y': return (1); case 'n': case 'N': return (0); default: return (0); } } return (0); } /* * Read lines from file and do something with each one. If option_null * is set, lines are terminated with zero bytes; otherwise, they're * terminated with newlines. * * This uses a self-sizing buffer to handle arbitrarily-long lines. * If the "process" function returns non-zero for any line, this * function will return non-zero after attempting to process all * remaining lines. */ int process_lines(struct bsdtar *bsdtar, const char *pathname, int (*process)(struct bsdtar *, const char *)) { FILE *f; char *buff, *buff_end, *line_start, *line_end, *p; size_t buff_length, new_buff_length, bytes_read, bytes_wanted; int separator; int ret; separator = bsdtar->option_null ? '\0' : '\n'; ret = 0; if (strcmp(pathname, "-") == 0) f = stdin; else f = fopen(pathname, "r"); if (f == NULL) bsdtar_errc(bsdtar, 1, errno, "Couldn't open %s", pathname); buff_length = 8192; buff = malloc(buff_length); if (buff == NULL) bsdtar_errc(bsdtar, 1, ENOMEM, "Can't read %s", pathname); line_start = line_end = buff_end = buff; for (;;) { /* Get some more data into the buffer. */ bytes_wanted = buff + buff_length - buff_end; bytes_read = fread(buff_end, 1, bytes_wanted, f); buff_end += bytes_read; /* Process all complete lines in the buffer. */ while (line_end < buff_end) { if (*line_end == separator) { *line_end = '\0'; if ((*process)(bsdtar, line_start) != 0) ret = -1; line_start = line_end + 1; line_end = line_start; } else line_end++; } if (feof(f)) break; if (ferror(f)) bsdtar_errc(bsdtar, 1, errno, "Can't read %s", pathname); if (line_start > buff) { /* Move a leftover fractional line to the beginning. */ memmove(buff, line_start, buff_end - line_start); buff_end -= line_start - buff; line_end -= line_start - buff; line_start = buff; } else { /* Line is too big; enlarge the buffer. */ 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); buff_end = p + (buff_end - buff); line_end = p + (line_end - buff); line_start = buff = p; } } /* At end-of-file, handle the final line. */ if (line_end > line_start) { *line_end = '\0'; if ((*process)(bsdtar, line_start) != 0) ret = -1; } free(buff); if (f != stdin) fclose(f); return (ret); } /*- * The logic here for -C attempts to avoid * chdir() as long as possible. For example: * "-C /foo -C /bar file" needs chdir("/bar") but not chdir("/foo") * "-C /foo -C bar file" needs chdir("/foo/bar") * "-C /foo -C bar /file1" does not need chdir() * "-C /foo -C bar /file1 file2" needs chdir("/foo/bar") before file2 * * The only correct way to handle this is to record a "pending" chdir * request and combine multiple requests intelligently until we * need to process a non-absolute file. set_chdir() adds the new dir * to the pending list; do_chdir() actually executes any pending chdir. * * This way, programs that build tar command lines don't have to worry * about -C with non-existent directories; such requests will only * fail if the directory must be accessed. */ void set_chdir(struct bsdtar *bsdtar, const char *newdir) { if (newdir[0] == '/') { /* The -C /foo -C /bar case; dump first one. */ free(bsdtar->pending_chdir); bsdtar->pending_chdir = NULL; } if (bsdtar->pending_chdir == NULL) /* Easy case: no previously-saved dir. */ bsdtar->pending_chdir = strdup(newdir); else { /* The -C /foo -C bar case; concatenate */ char *old_pending = bsdtar->pending_chdir; size_t old_len = strlen(old_pending); bsdtar->pending_chdir = malloc(old_len + strlen(newdir) + 2); if (old_pending[old_len - 1] == '/') old_pending[old_len - 1] = '\0'; if (bsdtar->pending_chdir != NULL) sprintf(bsdtar->pending_chdir, "%s/%s", old_pending, newdir); free(old_pending); } if (bsdtar->pending_chdir == NULL) bsdtar_errc(bsdtar, 1, errno, "No memory"); } void do_chdir(struct bsdtar *bsdtar) { if (bsdtar->pending_chdir == NULL) return; if (chdir(bsdtar->pending_chdir) != 0) { bsdtar_errc(bsdtar, 1, 0, "could not chdir to '%s'\n", bsdtar->pending_chdir); } free(bsdtar->pending_chdir); bsdtar->pending_chdir = NULL; } const char * strip_components(const char *path, int elements) { const char *p = path; while (elements > 0) { switch (*p++) { case '/': elements--; path = p; break; case '\0': /* Path is too short, skip it. */ return (NULL); } } while (*path == '/') ++path; if (*path == '\0') return (NULL); return (path); } /* * Handle --strip-components and any future path-rewriting options. * Returns non-zero if the pathname should not be extracted. * * TODO: Support pax-style regex path rewrites. */ int edit_pathname(struct bsdtar *bsdtar, struct archive_entry *entry) { const char *name = archive_entry_pathname(entry); #if HAVE_REGEX_H char *subst_name; int r; #endif #if HAVE_REGEX_H r = apply_substitution(bsdtar, name, &subst_name, 0); if (r == -1) { bsdtar_warnc(bsdtar, 0, "Invalid substitution, skipping entry"); return 1; } if (r == 1) { archive_entry_copy_pathname(entry, subst_name); if (*subst_name == '\0') { free(subst_name); return -1; } else free(subst_name); 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 substitution, 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 substitution, 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) { const char *linkname = archive_entry_hardlink(entry); name = strip_components(name, bsdtar->strip_components); if (name == NULL) return (1); if (linkname != NULL) { linkname = strip_components(linkname, bsdtar->strip_components); if (linkname == NULL) return (1); archive_entry_copy_hardlink(entry, linkname); } } /* Strip redundant leading '/' characters. */ while (name[0] == '/' && name[1] == '/') name++; - /* Strip leading '/' unless user has asked us not to. */ - if (name[0] == '/' && !bsdtar->option_absolute_paths) { - /* Generate a warning the first time this happens. */ - if (!bsdtar->warned_lead_slash) { - bsdtar_warnc(bsdtar, 0, - "Removing leading '/' from member names"); - bsdtar->warned_lead_slash = 1; + /* By default, don't write or restore absolute pathnames. */ + if (!bsdtar->option_absolute_paths) { + /* Strip Windows drive letters. */ + if (((name[0] >= 'A' && name[0] <= 'Z') + || (name[0] >= 'a' && name[0] <= 'z')) + && name[1] == ':' + && (name[2] == '/' || name[2] == '\\')) + { + /* Generate a warning the first time this happens. */ + if (!bsdtar->warned_lead_slash) { + bsdtar_warnc(bsdtar, 0, + "Removing leading drive letter from member names"); + bsdtar->warned_lead_slash = 1; + } + name += 3; + while (*name == '/' || *name == '\\') + ++name; + /* Special case: Stripping everything yields ".". */ + if (*name == '\0') + name = "."; + } + + /* Strip leading '/'. */ + if (name[0] == '/') { + /* Generate a warning the first time this happens. */ + if (!bsdtar->warned_lead_slash) { + bsdtar_warnc(bsdtar, 0, + "Removing leading '/' from member names"); + bsdtar->warned_lead_slash = 1; + } + name++; + /* Special case: Stripping everything yields ".". */ + if (*name == '\0') + name = "."; } - name++; - /* Special case: Stripping leading '/' from "/" yields ".". */ - if (*name == '\0') - name = "."; } /* Safely replace name in archive_entry. */ if (name != archive_entry_pathname(entry)) { char *q = strdup(name); archive_entry_copy_pathname(entry, q); free(q); } return (0); } /* * Like strcmp(), but try to be a little more aware of the fact that * we're comparing two paths. Right now, it just handles leading * "./" and trailing '/' specially, so that "a/b/" == "./a/b" * * TODO: Make this better, so that "./a//b/./c/" == "a/b/c" * TODO: After this works, push it down into libarchive. * TODO: Publish the path normalization routines in libarchive so * that bsdtar can normalize paths and use fast strcmp() instead * of this. */ int pathcmp(const char *a, const char *b) { /* Skip leading './' */ if (a[0] == '.' && a[1] == '/' && a[2] != '\0') a += 2; if (b[0] == '.' && b[1] == '/' && b[2] != '\0') b += 2; /* Find the first difference, or return (0) if none. */ while (*a == *b) { if (*a == '\0') return (0); a++; b++; } /* * If one ends in '/' and the other one doesn't, * they're the same. */ if (a[0] == '/' && a[1] == '\0' && b[0] == '\0') return (0); if (a[0] == '\0' && b[0] == '/' && b[1] == '\0') return (0); /* They're really different, return the correct sign. */ return (*(const unsigned char *)a - *(const unsigned char *)b); } diff --git a/usr.bin/tar/write.c b/usr.bin/tar/write.c index b64de1837730..dd534536c0ec 100644 --- a/usr.bin/tar/write.c +++ b/usr.bin/tar/write.c @@ -1,1044 +1,1049 @@ /*- * 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 "bsdtar_platform.h" __FBSDID("$FreeBSD$"); #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_ACL_H #include #endif #ifdef HAVE_SYS_IOCTL_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_ATTR_XATTR_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_FNMATCH_H #include #endif #ifdef HAVE_GRP_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #ifdef HAVE_LINUX_FS_H #include /* for Linux file flags */ #endif /* * Some Linux distributions have both linux/ext2_fs.h and ext2fs/ext2_fs.h. * As the include guards don't agree, the order of include is important. */ #ifdef HAVE_LINUX_EXT2_FS_H #include /* for Linux file flags */ #endif #if defined(HAVE_EXT2FS_EXT2_FS_H) && !defined(__CYGWIN__) /* This header exists but is broken on Cygwin. */ #include #endif #ifdef HAVE_PWD_H #include #endif #include #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #include "bsdtar.h" #include "tree.h" /* Size of buffer for holding file data prior to writing. */ #define FILEDATABUFLEN 65536 /* Fixed size of uname/gname caches. */ #define name_cache_size 101 static const char * const NO_NAME = "(noname)"; struct archive_dir_entry { struct archive_dir_entry *next; time_t mtime_sec; int mtime_nsec; char *name; }; struct archive_dir { struct archive_dir_entry *head, *tail; }; struct name_cache { int probes; int hits; size_t size; struct { id_t id; const char *name; } cache[name_cache_size]; }; static void add_dir_list(struct bsdtar *bsdtar, const char *path, time_t mtime_sec, int mtime_nsec); static int append_archive(struct bsdtar *, struct archive *, struct archive *ina); static int append_archive_filename(struct bsdtar *, struct archive *, const char *fname); static void archive_names_from_file(struct bsdtar *bsdtar, struct archive *a); static int archive_names_from_file_helper(struct bsdtar *bsdtar, const char *line); static int copy_file_data(struct bsdtar *bsdtar, struct archive *a, struct archive *ina); static int new_enough(struct bsdtar *, const char *path, const struct stat *); static void test_for_append(struct bsdtar *); static void write_archive(struct archive *, struct bsdtar *); static void write_entry_backend(struct bsdtar *, struct archive *, struct archive_entry *); static int write_file_data(struct bsdtar *, struct archive *, struct archive_entry *, int fd); static void write_hierarchy(struct bsdtar *, struct archive *, const char *); void tar_mode_c(struct bsdtar *bsdtar) { struct archive *a; int r; if (*bsdtar->argv == NULL && bsdtar->names_from_file == NULL) bsdtar_errc(bsdtar, 1, 0, "no files or directories specified"); a = archive_write_new(); /* Support any format that the library supports. */ if (bsdtar->create_format == NULL) { r = archive_write_set_format_pax_restricted(a); bsdtar->create_format = "pax restricted"; } else { r = archive_write_set_format_by_name(a, bsdtar->create_format); } if (r != ARCHIVE_OK) { fprintf(stderr, "Can't use format %s: %s\n", bsdtar->create_format, archive_error_string(a)); usage(bsdtar); } /* * If user explicitly set the block size, then assume they * want the last block padded as well. Otherwise, use the * default block size and accept archive_write_open_file()'s * default padding decisions. */ if (bsdtar->bytes_per_block != 0) { archive_write_set_bytes_per_block(a, bsdtar->bytes_per_block); archive_write_set_bytes_in_last_block(a, bsdtar->bytes_per_block); } else archive_write_set_bytes_per_block(a, DEFAULT_BYTES_PER_BLOCK); if (bsdtar->compress_program) { archive_write_set_compression_program(a, bsdtar->compress_program); } else { switch (bsdtar->create_compression) { case 0: archive_write_set_compression_none(a); break; #ifdef HAVE_LIBBZ2 case 'j': case 'y': archive_write_set_compression_bzip2(a); break; #endif #ifdef HAVE_LIBZ case 'z': archive_write_set_compression_gzip(a); break; #endif case 'Z': archive_write_set_compression_compress(a); break; default: bsdtar_errc(bsdtar, 1, 0, "Unrecognized compression option -%c", bsdtar->create_compression); } } r = archive_write_open_file(a, bsdtar->filename); if (r != ARCHIVE_OK) bsdtar_errc(bsdtar, 1, 0, archive_error_string(a)); write_archive(a, bsdtar); } /* * Same as 'c', except we only support tar or empty formats in * uncompressed files on disk. */ void tar_mode_r(struct bsdtar *bsdtar) { off_t end_offset; int format; struct archive *a; struct archive_entry *entry; int r; /* Sanity-test some arguments and the file. */ test_for_append(bsdtar); format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED; bsdtar->fd = open(bsdtar->filename, O_RDWR | O_CREAT, 0666); if (bsdtar->fd < 0) bsdtar_errc(bsdtar, 1, errno, "Cannot open %s", bsdtar->filename); a = archive_read_new(); archive_read_support_compression_all(a); archive_read_support_format_tar(a); archive_read_support_format_gnutar(a); r = archive_read_open_fd(a, bsdtar->fd, 10240); if (r != ARCHIVE_OK) bsdtar_errc(bsdtar, 1, archive_errno(a), "Can't read archive %s: %s", bsdtar->filename, archive_error_string(a)); while (0 == archive_read_next_header(a, &entry)) { if (archive_compression(a) != ARCHIVE_COMPRESSION_NONE) { archive_read_finish(a); close(bsdtar->fd); bsdtar_errc(bsdtar, 1, 0, "Cannot append to compressed archive."); } /* Keep going until we hit end-of-archive */ format = archive_format(a); } end_offset = archive_read_header_position(a); archive_read_finish(a); /* Re-open archive for writing */ a = archive_write_new(); archive_write_set_compression_none(a); /* * Set the format to be used for writing. To allow people to * extend empty files, we need to allow them to specify the format, * which opens the possibility that they will specify a format that * doesn't match the existing format. Hence, the following bit * of arcane ugliness. */ if (bsdtar->create_format != NULL) { /* If the user requested a format, use that, but ... */ archive_write_set_format_by_name(a, bsdtar->create_format); /* ... complain if it's not compatible. */ format &= ARCHIVE_FORMAT_BASE_MASK; if (format != (int)(archive_format(a) & ARCHIVE_FORMAT_BASE_MASK) && format != ARCHIVE_FORMAT_EMPTY) { bsdtar_errc(bsdtar, 1, 0, "Format %s is incompatible with the archive %s.", bsdtar->create_format, bsdtar->filename); } } else { /* * Just preserve the current format, with a little care * for formats that libarchive can't write. */ if (format == ARCHIVE_FORMAT_TAR_GNUTAR) /* TODO: When gtar supports pax, use pax restricted. */ format = ARCHIVE_FORMAT_TAR_USTAR; if (format == ARCHIVE_FORMAT_EMPTY) format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED; archive_write_set_format(a, format); } lseek(bsdtar->fd, end_offset, SEEK_SET); /* XXX check return val XXX */ archive_write_open_fd(a, bsdtar->fd); /* XXX check return val XXX */ write_archive(a, bsdtar); /* XXX check return val XXX */ close(bsdtar->fd); bsdtar->fd = -1; } void tar_mode_u(struct bsdtar *bsdtar) { off_t end_offset; struct archive *a; struct archive_entry *entry; int format; struct archive_dir_entry *p; struct archive_dir archive_dir; bsdtar->archive_dir = &archive_dir; memset(&archive_dir, 0, sizeof(archive_dir)); format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED; /* Sanity-test some arguments and the file. */ test_for_append(bsdtar); bsdtar->fd = open(bsdtar->filename, O_RDWR); if (bsdtar->fd < 0) bsdtar_errc(bsdtar, 1, errno, "Cannot open %s", bsdtar->filename); a = archive_read_new(); archive_read_support_compression_all(a); archive_read_support_format_tar(a); archive_read_support_format_gnutar(a); if (archive_read_open_fd(a, bsdtar->fd, bsdtar->bytes_per_block != 0 ? bsdtar->bytes_per_block : DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) { bsdtar_errc(bsdtar, 1, 0, "Can't open %s: %s", bsdtar->filename, archive_error_string(a)); } /* Build a list of all entries and their recorded mod times. */ while (0 == archive_read_next_header(a, &entry)) { if (archive_compression(a) != ARCHIVE_COMPRESSION_NONE) { archive_read_finish(a); close(bsdtar->fd); bsdtar_errc(bsdtar, 1, 0, "Cannot append to compressed archive."); } add_dir_list(bsdtar, archive_entry_pathname(entry), archive_entry_mtime(entry), archive_entry_mtime_nsec(entry)); /* Record the last format determination we see */ format = archive_format(a); /* Keep going until we hit end-of-archive */ } end_offset = archive_read_header_position(a); archive_read_finish(a); /* Re-open archive for writing. */ a = archive_write_new(); archive_write_set_compression_none(a); /* * Set format to same one auto-detected above, except that * we don't write GNU tar format, so use ustar instead. */ if (format == ARCHIVE_FORMAT_TAR_GNUTAR) format = ARCHIVE_FORMAT_TAR_USTAR; archive_write_set_format(a, format); if (bsdtar->bytes_per_block != 0) { archive_write_set_bytes_per_block(a, bsdtar->bytes_per_block); archive_write_set_bytes_in_last_block(a, bsdtar->bytes_per_block); } else archive_write_set_bytes_per_block(a, DEFAULT_BYTES_PER_BLOCK); lseek(bsdtar->fd, end_offset, SEEK_SET); ftruncate(bsdtar->fd, end_offset); archive_write_open_fd(a, bsdtar->fd); write_archive(a, bsdtar); close(bsdtar->fd); bsdtar->fd = -1; while (bsdtar->archive_dir->head != NULL) { p = bsdtar->archive_dir->head->next; free(bsdtar->archive_dir->head->name); free(bsdtar->archive_dir->head); bsdtar->archive_dir->head = p; } bsdtar->archive_dir->tail = NULL; } /* * Write user-specified files/dirs to opened archive. */ static void write_archive(struct archive *a, struct bsdtar *bsdtar) { const char *arg; struct archive_entry *entry, *sparse_entry; /* We want to catch SIGINFO and SIGUSR1. */ siginfo_init(bsdtar); /* Allocate a buffer for file data. */ if ((bsdtar->buff = malloc(FILEDATABUFLEN)) == NULL) bsdtar_errc(bsdtar, 1, 0, "cannot allocate memory"); 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->diskreader = archive_read_disk_new()) == NULL) bsdtar_errc(bsdtar, 1, 0, "Cannot create read_disk object"); archive_read_disk_set_standard_lookup(bsdtar->diskreader); if (bsdtar->names_from_file != NULL) archive_names_from_file(bsdtar, a); while (*bsdtar->argv) { arg = *bsdtar->argv; if (arg[0] == '-' && arg[1] == 'C') { arg += 2; if (*arg == '\0') { bsdtar->argv++; arg = *bsdtar->argv; if (arg == NULL) { bsdtar_warnc(bsdtar, 1, 0, "Missing argument for -C"); bsdtar->return_value = 1; goto cleanup; } } set_chdir(bsdtar, arg); } else { if (*arg != '/' && (arg[0] != '@' || arg[1] != '/')) do_chdir(bsdtar); /* Handle a deferred -C */ if (*arg == '@') { if (append_archive_filename(bsdtar, a, arg + 1) != 0) break; } else +#ifdef _WIN32 + write_hierarchy_win(bsdtar, a, arg, + write_hierarchy); +#else write_hierarchy(bsdtar, a, arg); +#endif } bsdtar->argv++; } entry = NULL; archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry); while (entry != NULL) { write_entry_backend(bsdtar, a, entry); archive_entry_free(entry); entry = NULL; archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry); } if (archive_write_close(a)) { bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); bsdtar->return_value = 1; } cleanup: /* Free file data buffer. */ free(bsdtar->buff); archive_entry_linkresolver_free(bsdtar->resolver); bsdtar->resolver = NULL; archive_read_finish(bsdtar->diskreader); bsdtar->diskreader = NULL; if (bsdtar->option_totals) { fprintf(stderr, "Total bytes written: " BSDTAR_FILESIZE_PRINTF "\n", (BSDTAR_FILESIZE_TYPE)archive_position_compressed(a)); } archive_write_finish(a); /* Restore old SIGINFO + SIGUSR1 handlers. */ siginfo_done(bsdtar); } /* * Archive names specified in file. * * Unless --null was specified, a line containing exactly "-C" will * cause the next line to be a directory to pass to chdir(). If * --null is specified, then a line "-C" is just another filename. */ void archive_names_from_file(struct bsdtar *bsdtar, struct archive *a) { bsdtar->archive = a; bsdtar->next_line_is_dir = 0; process_lines(bsdtar, bsdtar->names_from_file, archive_names_from_file_helper); if (bsdtar->next_line_is_dir) bsdtar_errc(bsdtar, 1, errno, "Unexpected end of filename list; " "directory expected after -C"); } static int archive_names_from_file_helper(struct bsdtar *bsdtar, const char *line) { if (bsdtar->next_line_is_dir) { set_chdir(bsdtar, line); bsdtar->next_line_is_dir = 0; } else if (!bsdtar->option_null && strcmp(line, "-C") == 0) bsdtar->next_line_is_dir = 1; else { if (*line != '/') do_chdir(bsdtar); /* Handle a deferred -C */ write_hierarchy(bsdtar, bsdtar->archive, line); } return (0); } /* * Copy from specified archive to current archive. Returns non-zero * for write errors (which force us to terminate the entire archiving * operation). If there are errors reading the input archive, we set * bsdtar->return_value but return zero, so the overall archiving * operation will complete and return non-zero. */ static int append_archive_filename(struct bsdtar *bsdtar, struct archive *a, const char *filename) { struct archive *ina; int rc; if (strcmp(filename, "-") == 0) filename = NULL; /* Library uses NULL for stdio. */ ina = archive_read_new(); archive_read_support_format_all(ina); archive_read_support_compression_all(ina); if (archive_read_open_file(ina, filename, 10240)) { bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(ina)); bsdtar->return_value = 1; return (0); } rc = append_archive(bsdtar, a, ina); if (archive_errno(ina)) { bsdtar_warnc(bsdtar, 0, "Error reading archive %s: %s", filename, archive_error_string(ina)); bsdtar->return_value = 1; } archive_read_finish(ina); return (rc); } static int append_archive(struct bsdtar *bsdtar, struct archive *a, struct archive *ina) { struct archive_entry *in_entry; int e; while (0 == archive_read_next_header(ina, &in_entry)) { if (!new_enough(bsdtar, archive_entry_pathname(in_entry), archive_entry_stat(in_entry))) continue; if (excluded(bsdtar, archive_entry_pathname(in_entry))) continue; if (bsdtar->option_interactive && !yes("copy '%s'", archive_entry_pathname(in_entry))) continue; if (bsdtar->verbose) safe_fprintf(stderr, "a %s", archive_entry_pathname(in_entry)); siginfo_setinfo(bsdtar, "copying", archive_entry_pathname(in_entry), archive_entry_size(in_entry)); siginfo_printinfo(bsdtar, 0); e = archive_write_header(a, in_entry); if (e != ARCHIVE_OK) { if (!bsdtar->verbose) bsdtar_warnc(bsdtar, 0, "%s: %s", archive_entry_pathname(in_entry), archive_error_string(a)); else fprintf(stderr, ": %s", archive_error_string(a)); } if (e == ARCHIVE_FATAL) exit(1); 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"); } /* Note: If we got here, we saw no write errors, so return success. */ return (0); } /* Helper function to copy data between archives. */ static int copy_file_data(struct bsdtar *bsdtar, struct archive *a, struct archive *ina) { ssize_t bytes_read; ssize_t bytes_written; off_t progress = 0; bytes_read = archive_read_data(ina, bsdtar->buff, FILEDATABUFLEN); while (bytes_read > 0) { siginfo_printinfo(bsdtar, progress); bytes_written = archive_write_data(a, bsdtar->buff, bytes_read); if (bytes_written < bytes_read) { bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); return (-1); } progress += bytes_written; bytes_read = archive_read_data(ina, bsdtar->buff, FILEDATABUFLEN); } return (0); } /* * Add the file or dir hierarchy named by 'path' to the archive */ static void write_hierarchy(struct bsdtar *bsdtar, struct archive *a, const char *path) { struct archive_entry *entry = NULL, *spare_entry = NULL; struct tree *tree; char symlink_mode = bsdtar->symlink_mode; dev_t first_dev = 0; int dev_recorded = 0; int tree_ret; tree = tree_open(path); if (!tree) { bsdtar_warnc(bsdtar, errno, "%s: Cannot open", path); bsdtar->return_value = 1; return; } while ((tree_ret = tree_next(tree))) { int r; const char *name = tree_current_path(tree); const struct stat *st = NULL; /* info to use for this entry */ const struct stat *lst = NULL; /* lstat() information */ int descend; if (tree_ret == TREE_ERROR_FATAL) bsdtar_errc(bsdtar, 1, tree_errno(tree), "%s: Unable to continue traversing directory tree", name); if (tree_ret == TREE_ERROR_DIR) { bsdtar_warnc(bsdtar, errno, "%s: Couldn't visit directory", name); bsdtar->return_value = 1; } if (tree_ret != TREE_REGULAR) continue; /* * If this file/dir is excluded by a filename * pattern, skip it. */ if (excluded(bsdtar, name)) continue; /* * Get lstat() info from the tree library. */ lst = tree_current_lstat(tree); if (lst == NULL) { /* Couldn't lstat(); must not exist. */ bsdtar_warnc(bsdtar, errno, "%s: Cannot stat", name); /* Return error if files disappear during traverse. */ bsdtar->return_value = 1; continue; } /* * Distinguish 'L'/'P'/'H' symlink following. */ switch(symlink_mode) { case 'H': /* 'H': After the first item, rest like 'P'. */ symlink_mode = 'P'; /* 'H': First item (from command line) like 'L'. */ /* FALLTHROUGH */ case 'L': /* 'L': Do descend through a symlink to dir. */ descend = tree_current_is_dir(tree); /* 'L': Follow symlinks to files. */ archive_read_disk_set_symlink_logical(bsdtar->diskreader); /* 'L': Archive symlinks as targets, if we can. */ st = tree_current_stat(tree); if (st != NULL) break; /* If stat fails, we have a broken symlink; * in that case, don't follow the link. */ /* FALLTHROUGH */ default: /* 'P': Don't descend through a symlink to dir. */ descend = tree_current_is_physical_dir(tree); /* 'P': Don't follow symlinks to files. */ archive_read_disk_set_symlink_physical(bsdtar->diskreader); /* 'P': Archive symlinks as symlinks. */ st = lst; break; } /* * If user has asked us not to cross mount points, * then don't descend into into a dir on a different * device. */ if (!dev_recorded) { first_dev = lst->st_dev; dev_recorded = 1; } if (bsdtar->option_dont_traverse_mounts) { if (lst->st_dev != first_dev) descend = 0; } /* * In -u mode, check that the file is newer than what's * already in the archive; in all modes, obey --newerXXX flags. */ if (!new_enough(bsdtar, name, st)) continue; archive_entry_free(entry); entry = archive_entry_new(); archive_entry_set_pathname(entry, name); archive_entry_copy_sourcepath(entry, tree_current_access_path(tree)); /* Populate the archive_entry with metadata from the disk. */ /* XXX TODO: Arrange to open a regular file before * calling this so we can pass in an fd and shorten * the race to query metadata. The linkify dance * makes this more complex than it might sound. */ r = archive_read_disk_entry_from_file(bsdtar->diskreader, entry, -1, st); if (r != ARCHIVE_OK) bsdtar_warnc(bsdtar, archive_errno(bsdtar->diskreader), archive_error_string(bsdtar->diskreader)); if (r < ARCHIVE_WARN) continue; /* XXX TODO: Just use flag data from entry; avoid the * duplicate check here. */ /* * If this file/dir is flagged "nodump" and we're * honoring such flags, skip this file/dir. */ #ifdef HAVE_STRUCT_STAT_ST_FLAGS /* BSD systems store flags in struct stat */ if (bsdtar->option_honor_nodump && (lst->st_flags & UF_NODUMP)) continue; #endif #if defined(EXT2_IOC_GETFLAGS) && defined(EXT2_NODUMP_FL) /* Linux uses ioctl to read flags. */ if (bsdtar->option_honor_nodump) { int fd = open(name, O_RDONLY | O_NONBLOCK); if (fd >= 0) { unsigned long fflags; int r = ioctl(fd, EXT2_IOC_GETFLAGS, &fflags); close(fd); if (r >= 0 && (fflags & EXT2_NODUMP_FL)) continue; } } #endif /* * If the user vetoes this file/directory, skip it. * We want this to be fairly late; if some other * check would veto this file, we shouldn't bother * the user with it. */ if (bsdtar->option_interactive && !yes("add '%s'", name)) continue; /* Note: if user vetoes, we won't descend. */ if (descend && !bsdtar->option_no_subdirs) tree_descend(tree); /* * Rewrite the pathname to be archived. If rewrite * fails, skip the entry. */ if (edit_pathname(bsdtar, entry)) continue; /* Display entry as we process it. * This format is required by SUSv2. */ if (bsdtar->verbose) safe_fprintf(stderr, "a %s", archive_entry_pathname(entry)); /* Non-regular files get archived with zero size. */ if (!S_ISREG(st->st_mode)) archive_entry_set_size(entry, 0); /* Record what we're doing, for SIGINFO / SIGUSR1. */ siginfo_setinfo(bsdtar, "adding", archive_entry_pathname(entry), archive_entry_size(entry)); archive_entry_linkify(bsdtar->resolver, &entry, &spare_entry); /* Handle SIGINFO / SIGUSR1 request if one was made. */ siginfo_printinfo(bsdtar, 0); while (entry != NULL) { write_entry_backend(bsdtar, a, entry); archive_entry_free(entry); entry = spare_entry; spare_entry = NULL; } if (bsdtar->verbose) fprintf(stderr, "\n"); } archive_entry_free(entry); tree_close(tree); } /* * Backend for write_entry. */ static void write_entry_backend(struct bsdtar *bsdtar, struct archive *a, struct archive_entry *entry) { int fd = -1; int e; if (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, entry, fd)) exit(1); } /* * If we opened a file, close it now even if there was an error * which made us decide not to write the archive body. */ if (fd >= 0) close(fd); } /* Helper function to copy file to archive. */ static int write_file_data(struct bsdtar *bsdtar, struct archive *a, struct archive_entry *entry, int fd) { ssize_t bytes_read; ssize_t bytes_written; off_t progress = 0; bytes_read = read(fd, bsdtar->buff, FILEDATABUFLEN); while (bytes_read > 0) { siginfo_printinfo(bsdtar, progress); bytes_written = archive_write_data(a, bsdtar->buff, bytes_read); if (bytes_written < 0) { /* Write failed; this is bad */ bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); return (-1); } if (bytes_written < bytes_read) { /* Write was truncated; warn but continue. */ bsdtar_warnc(bsdtar, 0, "%s: Truncated write; file may have grown while being archived.", archive_entry_pathname(entry)); return (0); } progress += bytes_written; bytes_read = read(fd, bsdtar->buff, FILEDATABUFLEN); } return 0; } /* * Test if the specified file is new enough to include in the archive. */ int new_enough(struct bsdtar *bsdtar, const char *path, const struct stat *st) { struct archive_dir_entry *p; /* * If this file/dir is excluded by a time comparison, skip it. */ if (bsdtar->newer_ctime_sec > 0) { if (st->st_ctime < bsdtar->newer_ctime_sec) return (0); /* Too old, skip it. */ if (st->st_ctime == bsdtar->newer_ctime_sec && ARCHIVE_STAT_CTIME_NANOS(st) <= bsdtar->newer_ctime_nsec) return (0); /* Too old, skip it. */ } if (bsdtar->newer_mtime_sec > 0) { if (st->st_mtime < bsdtar->newer_mtime_sec) return (0); /* Too old, skip it. */ if (st->st_mtime == bsdtar->newer_mtime_sec && ARCHIVE_STAT_MTIME_NANOS(st) <= bsdtar->newer_mtime_nsec) return (0); /* Too old, skip it. */ } /* * In -u mode, we only write an entry if it's newer than * what was already in the archive. */ if (bsdtar->archive_dir != NULL && bsdtar->archive_dir->head != NULL) { for (p = bsdtar->archive_dir->head; p != NULL; p = p->next) { if (pathcmp(path, p->name)==0) return (p->mtime_sec < st->st_mtime || (p->mtime_sec == st->st_mtime && p->mtime_nsec < ARCHIVE_STAT_MTIME_NANOS(st))); } } /* If the file wasn't rejected, include it. */ return (1); } /* * Add an entry to the dir list for 'u' mode. * * XXX TODO: Make this fast. */ static void add_dir_list(struct bsdtar *bsdtar, const char *path, time_t mtime_sec, int mtime_nsec) { struct archive_dir_entry *p; /* * Search entire list to see if this file has appeared before. * If it has, override the timestamp data. */ p = bsdtar->archive_dir->head; while (p != NULL) { if (strcmp(path, p->name)==0) { p->mtime_sec = mtime_sec; p->mtime_nsec = mtime_nsec; return; } p = p->next; } p = malloc(sizeof(*p)); if (p == NULL) bsdtar_errc(bsdtar, 1, ENOMEM, "Can't read archive directory"); p->name = strdup(path); if (p->name == NULL) bsdtar_errc(bsdtar, 1, ENOMEM, "Can't read archive directory"); p->mtime_sec = mtime_sec; p->mtime_nsec = mtime_nsec; p->next = NULL; if (bsdtar->archive_dir->tail == NULL) { bsdtar->archive_dir->head = bsdtar->archive_dir->tail = p; } else { bsdtar->archive_dir->tail->next = p; bsdtar->archive_dir->tail = p; } } void test_for_append(struct bsdtar *bsdtar) { struct stat s; if (*bsdtar->argv == NULL && bsdtar->names_from_file == NULL) bsdtar_errc(bsdtar, 1, 0, "no files or directories specified"); if (bsdtar->filename == NULL) bsdtar_errc(bsdtar, 1, 0, "Cannot append to stdout."); if (bsdtar->create_compression != 0) bsdtar_errc(bsdtar, 1, 0, "Cannot append to %s with compression", bsdtar->filename); if (stat(bsdtar->filename, &s) != 0) return; if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode)) bsdtar_errc(bsdtar, 1, 0, "Cannot append to %s: not a regular file.", bsdtar->filename); }