Exemplo n.º 1
0
/*
 * Write a single entry to the archive.
 */
static void
write_entry(struct bsdtar *bsdtar, struct archive *a,
            struct archive_entry *entry)
{
    int e;

    e = archive_write_header(a, entry);
    if (e != ARCHIVE_OK) {
        if (bsdtar->verbose > 1) {
            safe_fprintf(stderr, "a ");
            list_item_verbose(bsdtar, stderr, entry);
            lafe_warnc(0, ": %s", archive_error_string(a));
        } else if (bsdtar->verbose > 0) {
            lafe_warnc(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 && archive_entry_size(entry) > 0) {
        if (copy_file_data_block(bsdtar, a, bsdtar->diskreader, entry))
            exit(1);
    }
}
Exemplo n.º 2
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 *raw_filename)
{
	struct archive *ina;
	const char *filename = raw_filename;
	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_filter_all(ina);
	set_reader_options(bsdtar, a);
	if (archive_read_open_filename(ina, filename,
					bsdtar->bytes_per_block)) {
		lafe_warnc(0, "%s", archive_error_string(ina));
		bsdtar->return_value = 1;
		return (0);
	}

	rc = append_archive(bsdtar, a, ina);

	if (rc != ARCHIVE_OK) {
		lafe_warnc(0, "Error reading archive %s: %s",
		    raw_filename, archive_error_string(ina));
		bsdtar->return_value = 1;
	}
	archive_read_free(ina);

	return (rc);
}
Exemplo n.º 3
0
/*
 * Exits if there's a fatal error.  Returns ARCHIVE_OK
 * if everything is kosher.
 */
static int
extract_data(struct archive *ar, struct archive *aw)
{
	int r;
	size_t size;
	const void *block;
	off_t offset;

	for (;;) {
		r = archive_read_data_block(ar, &block, &size, &offset);
		if (r == ARCHIVE_EOF)
			return (ARCHIVE_OK);
		if (r != ARCHIVE_OK) {
			lafe_warnc(archive_errno(ar),
			    "%s", archive_error_string(ar));
			exit(1);
		}
		r = archive_write_data_block(aw, block, size, offset);
		if (r != ARCHIVE_OK) {
			lafe_warnc(archive_errno(aw),
			    "%s", archive_error_string(aw));
			return (r);
		}
	}
}
Exemplo n.º 4
0
/*
 * Write a single entry to the archive.
 */
static void
write_entry(struct bsdtar *bsdtar, struct archive *a,
    struct archive_entry *entry)
{
	int fd = -1;
	int e;
	size_t align = 4096;

	if (archive_entry_size(entry) > 0) {
		const char *pathname = archive_entry_sourcepath(entry);
		/* TODO: Use O_DIRECT here and set 'align' to the
		 * actual filesystem block size.  As of July 2010, new
		 * directory-traversal code is going in that will make
		 * it much easier to track filesystem properties like
		 * this during the traversal. */
		fd = open(pathname, O_RDONLY | O_BINARY);
		align = 4096;
		if (fd == -1) {
			if (!bsdtar->verbose)
				lafe_warnc(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)
			lafe_warnc(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, align))
			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);
}
Exemplo n.º 5
0
static int
restore_time(struct cpio *cpio, struct archive_entry *entry,
    const char *name, int fd)
{
#ifndef HAVE_UTIMES
	static int warned = 0;

	(void)cpio; /* UNUSED */
	(void)entry; /* UNUSED */
	(void)name; /* UNUSED */

	if (!warned)
		lafe_warnc(0, "Can't restore access times on this platform");
	warned = 1;
	return (fd);
#else
#if defined(_WIN32) && !defined(__CYGWIN__)
	struct __timeval times[2];
#else
	struct timeval times[2];
#endif

	if (!cpio->option_atime_restore)
		return (fd);

        times[1].tv_sec = archive_entry_mtime(entry);
        times[1].tv_usec = archive_entry_mtime_nsec(entry) / 1000;

        times[0].tv_sec = archive_entry_atime(entry);
        times[0].tv_usec = archive_entry_atime_nsec(entry) / 1000;

#if defined(HAVE_FUTIMES) && !defined(__CYGWIN__)
        if (fd >= 0 && futimes(fd, times) == 0)
		return (fd);
#endif
	/*
	 * Some platform cannot restore access times if the file descriptor
	 * is still opened.
	 */
	if (fd >= 0) {
		close(fd);
		fd = -1;
	}

#ifdef HAVE_LUTIMES
        if (lutimes(name, times) != 0)
#else
        if ((AE_IFLNK != archive_entry_filetype(entry))
			&& utimes(name, times) != 0)
#endif
                lafe_warnc(errno, "Can't update time for %s", name);
#endif
	return (fd);
}
Exemplo n.º 6
0
/*
 * 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.
 */
static void
archive_names_from_file(struct bsdtar *bsdtar, struct archive *a)
{
	struct lafe_line_reader *lr;
	const char *line;

	bsdtar->next_line_is_dir = 0;

	lr = lafe_line_reader(bsdtar->names_from_file, bsdtar->option_null);
	while ((line = lafe_line_reader_next(lr)) != NULL) {
		if (bsdtar->next_line_is_dir) {
			if (*line != '\0')
				set_chdir(bsdtar, line);
			else {
				lafe_warnc(0,
				    "Meaningless argument for -C: ''");
				bsdtar->return_value = 1;
			}
			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, a, line);
		}
	}
	lafe_line_reader_free(lr);
	if (bsdtar->next_line_is_dir)
		lafe_errc(1, errno,
		    "Unexpected end of filename list; "
		    "directory expected after -C");
}
Exemplo n.º 7
0
/* Helper function to copy data between archives. */
static int
copy_file_data(struct bsdtar *bsdtar, struct archive *a,
    struct archive *ina, struct archive_entry *entry)
{
	ssize_t	bytes_read;
	ssize_t	bytes_written;
	int64_t	progress = 0;

	bytes_read = archive_read_data(ina, bsdtar->buff, bsdtar->buff_size);
	while (bytes_read > 0) {
		if (need_report())
			report_write(bsdtar, a, entry, progress);

		bytes_written = archive_write_data(a, bsdtar->buff,
		    bytes_read);
		if (bytes_written < bytes_read) {
			lafe_warnc(0, "%s", archive_error_string(a));
			return (-1);
		}
		progress += bytes_written;
		bytes_read = archive_read_data(ina, bsdtar->buff, bsdtar->buff_size);
	}

	return (0);
}
Exemplo n.º 8
0
/* Helper function to copy file to archive. */
static int
write_file_data(struct bsdtar *bsdtar, struct archive *a,
    struct archive_entry *entry, int fd, size_t align)
{
	ssize_t	bytes_read;
	ssize_t	bytes_written;
	int64_t	progress = 0;
	size_t  buff_size;
	char   *buff = bsdtar->buff;

	/* Round 'buff' up to the next multiple of 'align' and reduce
	 * 'buff_size' accordingly. */
	buff = (char *)((((uintptr_t)buff + align - 1) / align) * align);
	buff_size = bsdtar->buff + bsdtar->buff_size - buff;
	buff_size = (buff_size / align) * align;
	
	bytes_read = read(fd, buff, buff_size);
	while (bytes_read > 0) {
		if (need_report())
			report_write(bsdtar, a, entry, progress);

		bytes_written = archive_write_data(a, buff, bytes_read);
		if (bytes_written < 0) {
			/* Write failed; this is bad */
			lafe_warnc(0, "%s", archive_error_string(a));
			return (-1);
		}
		if (bytes_written < bytes_read) {
			/* Write was truncated; warn but continue. */
			lafe_warnc(0,
			    "%s: Truncated write; file may have grown while being archived.",
			    archive_entry_pathname(entry));
			return (0);
		}
		progress += bytes_written;
		bytes_read = read(fd, buff, buff_size);
	}
	return 0;
}
Exemplo n.º 9
0
int
lafe_unmatched_inclusions_warn(struct lafe_matching *matching, const char *msg)
{
	struct match *p;

	if (matching == NULL)
		return (0);

	for (p = matching->inclusions; p != NULL; p = p->next) {
		if (p->matches == 0)
			lafe_warnc(0, "%s: %s", p->pattern, msg);
	}

	return (matching->inclusions_unmatched_count);
}
Exemplo n.º 10
0
static int
unmatched_inclusions_warn(struct archive *matching, const char *msg)
{
	const char *p;
	int r;

	if (matching == NULL)
		return (0);

	while ((r = archive_match_path_unmatched_inclusions_next(
	    matching, &p)) == ARCHIVE_OK)
		lafe_warnc(0, "%s: %s", p, msg);
	if (r == ARCHIVE_FATAL)
		lafe_errc(1, errno, "Out of memory");

	return (archive_match_path_unmatched_inclusions(matching));
}
Exemplo n.º 11
0
static int
append_archive(struct bsdtar *bsdtar, struct archive *a, struct archive *ina)
{
    struct archive_entry *in_entry;
    int e;

    while (ARCHIVE_OK == (e = archive_read_next_header(ina, &in_entry))) {
        if (archive_match_excluded(bsdtar->matching, in_entry))
            continue;
        if (bsdtar->option_interactive &&
                !yes("copy '%s'", archive_entry_pathname(in_entry)))
            continue;
        if (bsdtar->verbose > 1) {
            safe_fprintf(stderr, "a ");
            list_item_verbose(bsdtar, stderr, in_entry);
        } else if (bsdtar->verbose > 0)
            safe_fprintf(stderr, "a %s",
                         archive_entry_pathname(in_entry));
        if (need_report())
            report_write(bsdtar, a, in_entry, 0);

        e = archive_write_header(a, in_entry);
        if (e != ARCHIVE_OK) {
            if (!bsdtar->verbose)
                lafe_warnc(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_block(bsdtar, a, ina, in_entry))
                exit(1);
        }

        if (bsdtar->verbose)
            fprintf(stderr, "\n");
    }

    return (e == ARCHIVE_EOF ? ARCHIVE_OK : e);
}
Exemplo n.º 12
0
static int
lookup_uname_helper(struct cpio *cpio, const char **name, id_t id)
{
	struct passwd	*pwent;

	(void)cpio; /* UNUSED */

	errno = 0;
	pwent = getpwuid((uid_t)id);
	if (pwent == NULL) {
		*name = NULL;
		if (errno != 0 && errno != ENOENT)
			lafe_warnc(errno, "getpwuid(%d) failed", id);
		return (errno);
	}

	*name = pwent->pw_name;
	return (0);
}
Exemplo n.º 13
0
static int
lookup_gname_helper(struct cpio *cpio, const char **name, id_t id)
{
	struct group	*grent;

	(void)cpio; /* UNUSED */

	errno = 0;
	grent = getgrgid((gid_t)id);
	if (grent == NULL) {
		*name = NULL;
		if (errno != 0)
			lafe_warnc(errno, "getgrgid(%d) failed", id);
		return (errno);
	}

	*name = grent->gr_name;
	return (0);
}
Exemplo n.º 14
0
int
main(int argc, char **argv)
{
    struct bsdtar       *bsdtar, bsdtar_storage;
    int          opt, t;
    char             option_o;
    char             possible_help_request;
    char             buff[16];
    time_t           now;

    /*
     * Use a pointer for consistency, but stack-allocated storage
     * for ease of cleanup.
     */
    _bsdtar = bsdtar = &bsdtar_storage;
    memset(bsdtar, 0, sizeof(*bsdtar));
    bsdtar->fd = -1; /* Mark as "unused" */
    option_o = 0;

#if defined(SIGINFO) || defined(SIGUSR1)
    { /* Catch SIGINFO and SIGUSR1, if they exist. */
        struct sigaction sa;
        sa.sa_handler = siginfo_handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
#ifdef SIGINFO
        if (sigaction(SIGINFO, &sa, NULL))
            lafe_errc(1, errno, "sigaction(SIGINFO) failed");
#endif
#ifdef SIGUSR1
        /* ... and treat SIGUSR1 the same way as SIGINFO. */
        if (sigaction(SIGUSR1, &sa, NULL))
            lafe_errc(1, errno, "sigaction(SIGUSR1) failed");
#endif
    }
#endif


    /* Need lafe_progname before calling lafe_warnc. */
    if (*argv == NULL)
        lafe_progname = "bsdtar";
    else {
#if defined(_WIN32) && !defined(__CYGWIN__)
        lafe_progname = strrchr(*argv, '\\');
#else
        lafe_progname = strrchr(*argv, '/');
#endif
        if (lafe_progname != NULL)
            lafe_progname++;
        else
            lafe_progname = *argv;
    }

    time(&now);

#if HAVE_SETLOCALE
    if (setlocale(LC_ALL, "") == NULL)
        lafe_warnc(0, "Failed to set default locale");
#endif
#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;

#ifndef _WIN32
    /* On POSIX systems, assume --same-owner and -p when run by
     * the root user.  This doesn't make any sense on Windows. */
    if (bsdtar->user_uid == 0) {
        /* --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;
    }
#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)
                lafe_errc(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 (lafe_exclude(&bsdtar->matching, bsdtar->optarg))
                lafe_errc(1, 0,
                    "Couldn't exclude %s\n", bsdtar->optarg);
            break;
        case OPTION_FORMAT: /* GNU tar, others */
            bsdtar->create_format = bsdtar->optarg;
            break;
        case OPTION_OPTIONS:
            bsdtar->option_options = 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();
            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 (lafe_include(&bsdtar->matching, bsdtar->optarg))
                lafe_errc(1, 0,
                    "Failed to add %s to inclusion list",
                    bsdtar->optarg);
            break;
        case 'j': /* GNU tar */
            if (bsdtar->create_compression != '\0')
                lafe_errc(1, 0,
                    "Can't specify both -%c and -%c", opt,
                    bsdtar->create_compression);
            bsdtar->create_compression = opt;
            break;
        case 'J': /* GNU tar 1.21 and later */
            if (bsdtar->create_compression != '\0')
                lafe_errc(1, 0,
                    "Can't specify both -%c and -%c", opt,
                    bsdtar->create_compression);
            bsdtar->create_compression = opt;
            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 OPTION_LZMA:
            if (bsdtar->create_compression != '\0')
                lafe_errc(1, 0,
                    "Can't specify both -%c and -%c", opt,
                    bsdtar->create_compression);
            bsdtar->create_compression = opt;
            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(now, bsdtar->optarg);
            break;
        case OPTION_NEWER_CTIME_THAN:
            {
                struct stat st;
                if (stat(bsdtar->optarg, &st) != 0)
                    lafe_errc(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(now, bsdtar->optarg);
            break;
        case OPTION_NEWER_MTIME_THAN:
            {
                struct stat st;
                if (stat(bsdtar->optarg, &st) != 0)
                    lafe_errc(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
            lafe_warnc(0,
                "-s is not supported by this version of bsdtar");
            usage();
#endif
            break;
        case OPTION_SAME_OWNER: /* GNU tar */
            bsdtar->extract_flags |= ARCHIVE_EXTRACT_OWNER;
            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 (lafe_exclude_from_file(&bsdtar->matching, bsdtar->optarg))
                lafe_errc(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 (bsdtar->create_compression != '\0')
                lafe_errc(1, 0,
                    "Can't specify both -%c and -%c", opt,
                    bsdtar->create_compression);
            bsdtar->create_compression = opt;
            break;
        case 'Z': /* GNU tar */
            if (bsdtar->create_compression != '\0')
                lafe_errc(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 (bsdtar->create_compression != '\0')
                lafe_errc(1, 0,
                    "Can't specify both -%c and -%c", opt,
                    bsdtar->create_compression);
            bsdtar->create_compression = opt;
            break;
        case OPTION_USE_COMPRESS_PROGRAM:
            bsdtar->compress_program = bsdtar->optarg;
            break;
        default:
            usage();
        }
    }

    /*
     * Sanity-check options.
     */

    /* If no "real" mode was specified, treat -h as --help. */
    if ((bsdtar->mode == '\0') && possible_help_request) {
        long_help();
        exit(0);
    }

    /* Otherwise, a mode is required. */
    if (bsdtar->mode == '\0')
        lafe_errc(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;
    }

    lafe_cleanup_exclusions(&bsdtar->matching);
#if HAVE_REGEX_H
    cleanup_substitution(bsdtar);
#endif

    if (bsdtar->return_value != 0)
        lafe_warnc(0,
            "Error exit delayed from previous errors.");
    return (bsdtar->return_value);
}
Exemplo n.º 15
0
/*-
 * The logic here for -C <dir> 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 defined(_WIN32) && !defined(__CYGWIN__)
	if (newdir[0] == '/' || newdir[0] == '\\' ||
	    /* Detect this type, for example, "C:\" or "C:/" */
	    (((newdir[0] >= 'a' && newdir[0] <= 'z') ||
	      (newdir[0] >= 'A' && newdir[0] <= 'Z')) &&
	    newdir[1] == ':' && (newdir[2] == '/' || newdir[2] == '\\'))) {
#else
	if (newdir[0] == '/') {
#endif
		/* 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)
		lafe_errc(1, errno, "No memory");
}

void
do_chdir(struct bsdtar *bsdtar)
{
	if (bsdtar->pending_chdir == NULL)
		return;

	if (chdir(bsdtar->pending_chdir) != 0) {
		lafe_errc(1, 0, "could not chdir to '%s'\n",
		    bsdtar->pending_chdir);
	}
	free(bsdtar->pending_chdir);
	bsdtar->pending_chdir = NULL;
}

static const char *
strip_components(const char *p, int elements)
{
	/* Skip as many elements as necessary. */
	while (elements > 0) {
		switch (*p++) {
		case '/':
#if defined(_WIN32) && !defined(__CYGWIN__)
		case '\\': /* Support \ path sep on Windows ONLY. */
#endif
			elements--;
			break;
		case '\0':
			/* Path is too short, skip it. */
			return (NULL);
		}
	}

	/* Skip any / characters.  This handles short paths that have
	 * additional / termination.  This also handles the case where
	 * the logic above stops in the middle of a duplicate //
	 * sequence (which would otherwise get converted to an
	 * absolute path). */
	for (;;) {
		switch (*p) {
		case '/':
#if defined(_WIN32) && !defined(__CYGWIN__)
		case '\\': /* Support \ path sep on Windows ONLY. */
#endif
			++p;
			break;
		case '\0':
			return (NULL);
		default:
			return (p);
		}
	}
}

static void
warn_strip_leading_char(struct bsdtar *bsdtar, const char *c)
{
	if (!bsdtar->warned_lead_slash) {
		lafe_warnc(0,
			   "Removing leading '%c' from member names",
			   c[0]);
		bsdtar->warned_lead_slash = 1;
	}
}

static void
warn_strip_drive_letter(struct bsdtar *bsdtar)
{
	if (!bsdtar->warned_lead_slash) {
		lafe_warnc(0,
			   "Removing leading drive letter from "
			   "member names");
		bsdtar->warned_lead_slash = 1;
	}
}

/*
 * Convert absolute path to non-absolute path by skipping leading
 * absolute path prefixes.
 */
static const char*
strip_absolute_path(struct bsdtar *bsdtar, const char *p)
{
	const char *rp;

	/* Remove leading "//./" or "//?/" or "//?/UNC/"
	 * (absolute path prefixes used by Windows API) */
	if ((p[0] == '/' || p[0] == '\\') &&
	    (p[1] == '/' || p[1] == '\\') &&
	    (p[2] == '.' || p[2] == '?') &&
	    (p[3] == '/' || p[3] == '\\'))
	{
		if (p[2] == '?' &&
		    (p[4] == 'U' || p[4] == 'u') &&
		    (p[5] == 'N' || p[5] == 'n') &&
		    (p[6] == 'C' || p[6] == 'c') &&
		    (p[7] == '/' || p[7] == '\\'))
			p += 8;
		else
			p += 4;
		warn_strip_drive_letter(bsdtar);
	}

	/* Remove multiple leading slashes and Windows drive letters. */
	do {
		rp = p;
		if (((p[0] >= 'a' && p[0] <= 'z') ||
		     (p[0] >= 'A' && p[0] <= 'Z')) &&
		    p[1] == ':') {
			p += 2;
			warn_strip_drive_letter(bsdtar);
		}

		/* Remove leading "/../", "/./", "//", etc. */
		while (p[0] == '/' || p[0] == '\\') {
			if (p[1] == '.' &&
			    p[2] == '.' &&
			    (p[3] == '/' || p[3] == '\\')) {
				p += 3; /* Remove "/..", leave "/" for next pass. */
			} else if (p[1] == '.' &&
				   (p[2] == '/' || p[2] == '\\')) {
				p += 2; /* Remove "/.", leave "/" for next pass. */
			} else
				p += 1; /* Remove "/". */
			warn_strip_leading_char(bsdtar, rp);
		}
	} while (rp != p);

	return (p);
}

/*
 * Handle --strip-components and any future path-rewriting options.
 * Returns non-zero if the pathname should not be extracted.
 *
 * Note: The rewrites are applied uniformly to pathnames and hardlink
 * names but not to symlink bodies.  This is deliberate: Symlink
 * bodies are not necessarily filenames.  Even when they are, they
 * need to be interpreted relative to the directory containing them,
 * so simple rewrites like this are rarely appropriate.
 *
 * TODO: Support pax-style regex path rewrites.
 */
int
edit_pathname(struct bsdtar *bsdtar, struct archive_entry *entry)
{
	const char *name = archive_entry_pathname(entry);
	const char *original_name = name;
	const char *hardlinkname = archive_entry_hardlink(entry);
	const char *original_hardlinkname = hardlinkname;
#if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H)
	char *subst_name;
	int r;

	/* Apply user-specified substitution to pathname. */
	r = apply_substitution(bsdtar, name, &subst_name, 0, 0);
	if (r == -1) {
		lafe_warnc(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);
		original_name = name;
	}

	/* Apply user-specified substitution to hardlink target. */
	if (hardlinkname != NULL) {
		r = apply_substitution(bsdtar, hardlinkname, &subst_name, 0, 1);
		if (r == -1) {
			lafe_warnc(0, "Invalid substitution, skipping entry");
			return 1;
		}
		if (r == 1) {
			archive_entry_copy_hardlink(entry, subst_name);
			free(subst_name);
		}
		hardlinkname = archive_entry_hardlink(entry);
		original_hardlinkname = hardlinkname;
	}

	/* Apply user-specified substitution to symlink body. */
	if (archive_entry_symlink(entry) != NULL) {
		r = apply_substitution(bsdtar, archive_entry_symlink(entry), &subst_name, 1, 0);
		if (r == -1) {
			lafe_warnc(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) {
		name = strip_components(name, bsdtar->strip_components);
		if (name == NULL)
			return (1);

		if (hardlinkname != NULL) {
			hardlinkname = strip_components(hardlinkname,
			    bsdtar->strip_components);
			if (hardlinkname == NULL)
				return (1);
		}
	}

	if (!bsdtar->option_absolute_paths) {
		/* By default, don't write or restore absolute pathnames. */
		name = strip_absolute_path(bsdtar, name);
		if (*name == '\0')
			name = ".";

		if (hardlinkname != NULL) {
			hardlinkname = strip_absolute_path(bsdtar, hardlinkname);
			if (*hardlinkname == '\0')
				return (1);
		}
	} else {
		/* Strip redundant leading '/' characters. */
		while (name[0] == '/' && name[1] == '/')
			name++;
	}

	/* Replace name in archive_entry. */
	if (name != original_name) {
		archive_entry_copy_pathname(entry, name);
	}
	if (hardlinkname != original_hardlinkname) {
		archive_entry_copy_hardlink(entry, hardlinkname);
	}
	return (0);
}

/*
 * It would be nice to just use printf() for formatting large numbers,
 * but the compatibility problems are quite a headache.  Hence the
 * following simple utility function.
 */
const char *
tar_i64toa(int64_t n0)
{
	static char buff[24];
	uint64_t n = n0 < 0 ? -n0 : n0;
	char *p = buff + sizeof(buff);

	*--p = '\0';
	do {
		*--p = '0' + (int)(n % 10);
	} while (n /= 10);
	if (n0 < 0)
		*--p = '-';
	return p;
}

/*
 * 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.
 *
 * Note: This is currently only used within write.c, so should
 * not handle \ path separators.
 */

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);
}

#define PPBUFF_SIZE 1024
const char *
passphrase_callback(struct archive *a, void *_client_data)
{
	struct bsdtar *bsdtar = (struct bsdtar *)_client_data;
	(void)a; /* UNUSED */

	if (bsdtar->ppbuff == NULL) {
		bsdtar->ppbuff = malloc(PPBUFF_SIZE);
		if (bsdtar->ppbuff == NULL)
			lafe_errc(1, errno, "Out of memory");
	}
	return lafe_readpassphrase("Enter passphrase:",
		bsdtar->ppbuff, PPBUFF_SIZE);
}

void
passphrase_free(char *ppbuff)
{
	if (ppbuff != NULL) {
		memset(ppbuff, 0, PPBUFF_SIZE);
		free(ppbuff);
	}
}

/*
 * 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'.
 */
void
list_item_verbose(struct bsdtar *bsdtar, FILE *out, struct archive_entry *entry)
{
	char			 tmp[100];
	size_t			 w;
	const char		*p;
	const char		*fmt;
	time_t			 tim;
	static time_t		 now;

	/*
	 * 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),
	    archive_entry_nlink(entry));

	/* Use uname if it's present, else uid. */
	p = archive_entry_uname(entry);
	if ((p == NULL) || (*p == '\0')) {
		sprintf(tmp, "%lu ",
		    (unsigned long)archive_entry_uid(entry));
		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)archive_entry_gid(entry));
		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 (archive_entry_filetype(entry) == AE_IFCHR
	    || archive_entry_filetype(entry) == AE_IFBLK) {
		sprintf(tmp, "%lu,%lu",
		    (unsigned long)archive_entry_rdevmajor(entry),
		    (unsigned long)archive_entry_rdevminor(entry));
	} else {
		strcpy(tmp, tar_i64toa(archive_entry_size(entry)));
	}
	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 = archive_entry_mtime(entry);
#define	HALF_YEAR (time_t)365 * 86400 / 2
#if defined(_WIN32) && !defined(__CYGWIN__)
#define	DAY_FMT  "%d"  /* Windows' strftime function does not support %e format. */
#else
#define	DAY_FMT  "%e"  /* Day number without leading zeros */
#endif
	if (tim < now - HALF_YEAR || tim > now + HALF_YEAR)
		fmt = bsdtar->day_first ? DAY_FMT " %b  %Y" : "%b " DAY_FMT "  %Y";
	else
		fmt = bsdtar->day_first ? DAY_FMT " %b %H:%M" : "%b " DAY_FMT " %H:%M";
	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 (archive_entry_symlink(entry)) /* Symbolic link */
		safe_fprintf(out, " -> %s", archive_entry_symlink(entry));
}
Exemplo n.º 16
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 *disk = bsdtar->diskreader;
	struct archive_entry *entry = NULL, *spare_entry = NULL;
	int r;

	r = archive_read_disk_open(disk, path);
	if (r != ARCHIVE_OK) {
		lafe_warnc(archive_errno(disk),
		    "%s", archive_error_string(disk));
		bsdtar->return_value = 1;
		return;
	}
	bsdtar->first_fs = -1;

	for (;;) {
		archive_entry_free(entry);
		entry = archive_entry_new();
		r = archive_read_next_header2(disk, entry);
		if (r == ARCHIVE_EOF)
			break;
		else if (r != ARCHIVE_OK) {
			lafe_warnc(archive_errno(disk),
			    "%s", archive_error_string(disk));
			if (r == ARCHIVE_FATAL) {
				bsdtar->return_value = 1;
				return;
			} else if (r < ARCHIVE_WARN)
				continue;
		}

		if (bsdtar->uid >= 0) {
			archive_entry_set_uid(entry, bsdtar->uid);
			if (!bsdtar->uname)
				archive_entry_set_uname(entry,
				    archive_read_disk_uname(bsdtar->diskreader,
					bsdtar->uid));
		}
		if (bsdtar->gid >= 0) {
			archive_entry_set_gid(entry, bsdtar->gid);
			if (!bsdtar->gname)
				archive_entry_set_gname(entry,
				    archive_read_disk_gname(bsdtar->diskreader,
					bsdtar->gid));
		}
		if (bsdtar->uname)
			archive_entry_set_uname(entry, bsdtar->uname);
		if (bsdtar->gname)
			archive_entry_set_gname(entry, bsdtar->gname);

		/*
		 * 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 (archive_entry_filetype(entry) != AE_IFREG)
			archive_entry_set_size(entry, 0);

		archive_entry_linkify(bsdtar->resolver, &entry, &spare_entry);

		while (entry != NULL) {
			write_file(bsdtar, a, entry);
			archive_entry_free(entry);
			entry = spare_entry;
			spare_entry = NULL;
		}

		if (bsdtar->verbose)
			fprintf(stderr, "\n");
	}
	archive_entry_free(entry);
	archive_read_close(disk);
}
Exemplo n.º 17
0
/* Helper function to copy file to archive. */
static int
copy_file_data_block(struct bsdtar *bsdtar, struct archive *a,
    struct archive *in_a, struct archive_entry *entry)
{
	size_t	bytes_read;
	ssize_t	bytes_written;
	int64_t	offset, progress = 0;
	char *null_buff = NULL;
	const void *buff;
	int r;

	while ((r = archive_read_data_block(in_a, &buff,
	    &bytes_read, &offset)) == ARCHIVE_OK) {
		if (need_report())
			report_write(bsdtar, a, entry, progress);

		if (offset > progress) {
			int64_t sparse = offset - progress;
			size_t ns;

			if (null_buff == NULL) {
				null_buff = bsdtar->buff;
				memset(null_buff, 0, bsdtar->buff_size);
			}

			while (sparse > 0) {
				if (sparse > (int64_t)bsdtar->buff_size)
					ns = bsdtar->buff_size;
				else
					ns = (size_t)sparse;
				bytes_written =
				    archive_write_data(a, null_buff, ns);
				if (bytes_written < 0) {
					/* Write failed; this is bad */
					lafe_warnc(0, "%s",
					     archive_error_string(a));
					return (-1);
				}
				if ((size_t)bytes_written < ns) {
					/* Write was truncated; warn but
					 * continue. */
					lafe_warnc(0,
					    "%s: Truncated write; file may "
					    "have grown while being archived.",
					    archive_entry_pathname(entry));
					return (0);
				}
				progress += bytes_written;
				sparse -= bytes_written;
			}
		}

		bytes_written = archive_write_data(a, buff, bytes_read);
		if (bytes_written < 0) {
			/* Write failed; this is bad */
			lafe_warnc(0, "%s", archive_error_string(a));
			return (-1);
		}
		if ((size_t)bytes_written < bytes_read) {
			/* Write was truncated; warn but continue. */
			lafe_warnc(0,
			    "%s: Truncated write; file may have grown "
			    "while being archived.",
			    archive_entry_pathname(entry));
			return (0);
		}
		progress += bytes_written;
	}
	if (r < ARCHIVE_WARN) {
		lafe_warnc(archive_errno(a), "%s", archive_error_string(a));
		return (-1);
	}
	return (0);
}
Exemplo n.º 18
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) {
		lafe_warnc(errno, "%s: Cannot open", path);
		bsdtar->return_value = 1;
		return;
	}

	while ((tree_ret = tree_next(tree)) != 0) {
		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)
			lafe_errc(1, tree_errno(tree),
			    "%s: Unable to continue traversing directory tree",
			    name);
		if (tree_ret == TREE_ERROR_DIR) {
			lafe_warnc(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 (lafe_excluded(bsdtar->matching, name))
			continue;

		/*
		 * Get lstat() info from the tree library.
		 */
		lst = tree_current_lstat(tree);
		if (lst == NULL) {
			/* Couldn't lstat(); must not exist. */
			lafe_warnc(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 (bsdtar->option_no_subdirs)
			descend = 0;

		/*
		 * Are we about to cross to a new filesystem?
		 */
		if (!dev_recorded) {
			/* This is the initial file system. */
			first_dev = lst->st_dev;
			dev_recorded = 1;
		} else if (lst->st_dev == first_dev) {
			/* The starting file system is always acceptable. */
		} else if (descend == 0) {
			/* We're not descending, so no need to check. */
		} else if (bsdtar->option_dont_traverse_mounts) {
			descend = 0;
		} else {
			/* We're prepared to cross a mount point. */

			/* XXX TODO: check whether this filesystem is
			 * synthetic and/or local.  Add a new
			 * --local-only option to skip non-local
			 * filesystems.  Skip synthetic filesystems
			 * regardless.
			 *
			 * The results should be cached, since
			 * tree.c doesn't usually visit a directory
			 * and the directory contents together.  A simple
			 * move-to-front list should perform quite well.
			 *
			 * This is going to be heavily OS dependent:
			 * FreeBSD's statfs() in conjunction with getvfsbyname()
			 * provides all of this; NetBSD's statvfs() does
			 * most of it; other systems will vary.
			 */
		}

		/*
		 * 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)) {
			if (!descend)
				continue;
			if (bsdtar->option_interactive &&
			    !yes("add '%s'", name))
				continue;
			tree_descend(tree);
			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. */
#if defined(_WIN32) && !defined(__CYGWIN__)
		/* TODO: tree.c uses stat(), which is badly broken
		 * on Windows.  To fix this, we should
		 * deprecate tree_current_stat() and provide a new
		 * call tree_populate_entry(t, entry).  This call
		 * would use stat() internally on POSIX and
		 * GetInfoByFileHandle() internally on Windows.
		 * This would be another step towards a tree-walker
		 * that can be integrated deep into libarchive.
		 * For now, just set st to NULL on Windows;
		 * archive_read_disk_entry_from_file() should
		 * be smart enough to use platform-appropriate
		 * ways to probe file information.
		 */
		st = NULL;
#endif
		r = archive_read_disk_entry_from_file(bsdtar->diskreader,
		    entry, -1, st);
		if (bsdtar->uid >= 0) {
			archive_entry_set_uid(entry, bsdtar->uid);
			if (!bsdtar->uname)
				archive_entry_set_uname(entry,
				    archive_read_disk_uname(bsdtar->diskreader,
					bsdtar->uid));
		}
		if (bsdtar->gid >= 0) {
			archive_entry_set_gid(entry, bsdtar->gid);
			if (!bsdtar->gname)
				archive_entry_set_gname(entry,
				    archive_read_disk_gname(bsdtar->diskreader,
					bsdtar->gid));
		}
		if (bsdtar->uname)
			archive_entry_set_uname(entry, bsdtar->uname);
		if (bsdtar->gname)
			archive_entry_set_gname(entry, bsdtar->gname);
		if (r != ARCHIVE_OK)
			lafe_warnc(archive_errno(bsdtar->diskreader),
			    "%s", 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.
		 */
#if defined(HAVE_STRUCT_STAT_ST_FLAGS) && defined(UF_NODUMP)
		/* 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 | O_BINARY);
			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

#ifdef __APPLE__
		if (bsdtar->enable_copyfile) {
			/* If we're using copyfile(), ignore "._XXX" files. */
			const char *bname = strrchr(name, '/');
			if (bname == NULL)
				bname = name;
			else
				++bname;
			if (bname[0] == '.' && bname[1] == '_')
				continue;
		} else {
			/* If not, drop the copyfile() data. */
			archive_entry_copy_mac_metadata(entry, NULL, 0);
		}
#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;

		if (descend)
			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 (archive_entry_filetype(entry) != AE_IFREG)
			archive_entry_set_size(entry, 0);

		archive_entry_linkify(bsdtar->resolver, &entry, &spare_entry);

		while (entry != NULL) {
			write_file(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);
}
Exemplo n.º 19
0
/*-
 * The logic here for -C <dir> 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 defined(_WIN32) && !defined(__CYGWIN__)
	if (newdir[0] == '/' || newdir[0] == '\\' ||
	    /* Detect this type, for example, "C:\" or "C:/" */
	    (((newdir[0] >= 'a' && newdir[0] <= 'z') ||
	      (newdir[0] >= 'A' && newdir[0] <= 'Z')) &&
	    newdir[1] == ':' && (newdir[2] == '/' || newdir[2] == '\\'))) {
#else
	if (newdir[0] == '/') {
#endif
		/* 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)
		lafe_errc(1, errno, "No memory");
}

void
do_chdir(struct bsdtar *bsdtar)
{
	if (bsdtar->pending_chdir == NULL)
		return;

	if (chdir(bsdtar->pending_chdir) != 0) {
		lafe_errc(1, 0, "could not chdir to '%s'\n",
		    bsdtar->pending_chdir);
	}
	free(bsdtar->pending_chdir);
	bsdtar->pending_chdir = NULL;
}

static const char *
strip_components(const char *p, int elements)
{
	/* Skip as many elements as necessary. */
	while (elements > 0) {
		switch (*p++) {
		case '/':
#if defined(_WIN32) && !defined(__CYGWIN__)
		case '\\': /* Support \ path sep on Windows ONLY. */
#endif
			elements--;
			break;
		case '\0':
			/* Path is too short, skip it. */
			return (NULL);
		}
	}

	/* Skip any / characters.  This handles short paths that have
	 * additional / termination.  This also handles the case where
	 * the logic above stops in the middle of a duplicate //
	 * sequence (which would otherwise get converted to an
	 * absolute path). */
	for (;;) {
		switch (*p) {
		case '/':
#if defined(_WIN32) && !defined(__CYGWIN__)
		case '\\': /* Support \ path sep on Windows ONLY. */
#endif
			++p;
			break;
		case '\0':
			return (NULL);
		default:
			return (p);
		}
	}
}

/*
 * 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 defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H)
	char *subst_name;
	int r;

	r = apply_substitution(bsdtar, name, &subst_name, 0, 0);
	if (r == -1) {
		lafe_warnc(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, 0, 1);
		if (r == -1) {
			lafe_warnc(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, 0);
		if (r == -1) {
			lafe_warnc(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);
		}
	}

	/* By default, don't write or restore absolute pathnames. */
	if (!bsdtar->option_absolute_paths) {
		const char *rp, *p = name;
		int slashonly = 1;

		/* Remove leading "//./" or "//?/" or "//?/UNC/"
		 * (absolute path prefixes used by Windows API) */
		if ((p[0] == '/' || p[0] == '\\') &&
		    (p[1] == '/' || p[1] == '\\') &&
		    (p[2] == '.' || p[2] == '?') &&
		    (p[3] == '/' || p[3] == '\\'))
		{
			if (p[2] == '?' &&
			    (p[4] == 'U' || p[4] == 'u') &&
			    (p[5] == 'N' || p[5] == 'n') &&
			    (p[6] == 'C' || p[6] == 'c') &&
			    (p[7] == '/' || p[7] == '\\'))
				p += 8;
			else
				p += 4;
			slashonly = 0;
		}
		do {
			rp = p;
			/* Remove leading drive letter from archives created
			 * on Windows. */
			if (((p[0] >= 'a' && p[0] <= 'z') ||
			     (p[0] >= 'A' && p[0] <= 'Z')) &&
				 p[1] == ':') {
				p += 2;
				slashonly = 0;
			}
			/* Remove leading "/../", "//", etc. */
			while (p[0] == '/' || p[0] == '\\') {
				if (p[1] == '.' && p[2] == '.' &&
					(p[3] == '/' || p[3] == '\\')) {
					p += 3; /* Remove "/..", leave "/"
							 * for next pass. */
					slashonly = 0;
				} else
					p += 1; /* Remove "/". */
			}
		} while (rp != p);

		if (p != name && !bsdtar->warned_lead_slash) {
			/* Generate a warning the first time this happens. */
			if (slashonly)
				lafe_warnc(0,
				    "Removing leading '%c' from member names",
				    name[0]);
			else
				lafe_warnc(0,
				    "Removing leading drive letter from "
				    "member names");
			bsdtar->warned_lead_slash = 1;
		}

		/* Special case: Stripping everything yields ".". */
		if (*p == '\0')
			name = ".";
		else
			name = p;
	} else {
		/* Strip redundant leading '/' characters. */
		while (name[0] == '/' && name[1] == '/')
			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);
}

/*
 * It would be nice to just use printf() for formatting large numbers,
 * but the compatibility problems are quite a headache.  Hence the
 * following simple utility function.
 */
const char *
tar_i64toa(int64_t n0)
{
	static char buff[24];
	uint64_t n = n0 < 0 ? -n0 : n0;
	char *p = buff + sizeof(buff);

	*--p = '\0';
	do {
		*--p = '0' + (int)(n % 10);
	} while (n /= 10);
	if (n0 < 0)
		*--p = '-';
	return p;
}

/*
 * 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.
 *
 * Note: This is currently only used within write.c, so should
 * not handle \ path separators.
 */

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);
}
Exemplo n.º 20
0
/*
 * 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;

	/* Choose a suitable copy buffer size */
	bsdtar->buff_size = 64 * 1024;
	while (bsdtar->buff_size < (size_t)bsdtar->bytes_per_block)
	  bsdtar->buff_size *= 2;
	/* Try to compensate for space we'll lose to alignment. */
	bsdtar->buff_size += 16 * 1024;

	/* Allocate a buffer for file data. */
	if ((bsdtar->buff = malloc(bsdtar->buff_size)) == NULL)
		lafe_errc(1, 0, "cannot allocate memory");

	if ((bsdtar->resolver = archive_entry_linkresolver_new()) == NULL)
		lafe_errc(1, 0, "cannot create link resolver");
	archive_entry_linkresolver_set_strategy(bsdtar->resolver,
	    archive_format(a));

	/* Create a read_disk object. */
	if ((bsdtar->diskreader = archive_read_disk_new()) == NULL)
		lafe_errc(1, 0, "Cannot create read_disk object");
	/* Tell the read_disk how handle symlink. */
	switch (bsdtar->symlink_mode) {
	case 'H':
		archive_read_disk_set_symlink_hybrid(bsdtar->diskreader);
		break;
	case 'L':
		archive_read_disk_set_symlink_logical(bsdtar->diskreader);
		break;
	default:
		archive_read_disk_set_symlink_physical(bsdtar->diskreader);
		break;
	}
	/* Register entry filters. */
	archive_read_disk_set_matching(bsdtar->diskreader,
	    bsdtar->matching, excluded_callback, bsdtar);
	archive_read_disk_set_metadata_filter_callback(
	    bsdtar->diskreader, metadata_filter, bsdtar);
	/* Set the behavior of archive_read_disk. */
	archive_read_disk_set_behavior(bsdtar->diskreader,
	    bsdtar->readdisk_flags);
	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) {
					lafe_warnc(0, "%s",
					    "Missing argument for -C");
					bsdtar->return_value = 1;
					goto cleanup;
				}
				if (*arg == '\0') {
					lafe_warnc(0,
					    "Meaningless 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
				write_hierarchy(bsdtar, a, arg);
		}
		bsdtar->argv++;
	}

	archive_read_disk_set_matching(bsdtar->diskreader, NULL, NULL, NULL);
	archive_read_disk_set_metadata_filter_callback(
	    bsdtar->diskreader, NULL, NULL);
	entry = NULL;
	archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry);
	while (entry != NULL) {
		int r;
		struct archive_entry *entry2;
		struct archive *disk = bsdtar->diskreader;

		/*
		 * This tricky code here is to correctly read the cotents
		 * of the entry because the disk reader bsdtar->diskreader
		 * is pointing at does not have any information about the
		 * entry by this time and using archive_read_data_block()
		 * with the disk reader consequently must fail. And we
		 * have to re-open the entry to read the contents.
		 */
		/* TODO: Work with -C option as well. */
		r = archive_read_disk_open(disk,
			archive_entry_sourcepath(entry));
		if (r != ARCHIVE_OK) {
			lafe_warnc(archive_errno(disk),
			    "%s", archive_error_string(disk));
			bsdtar->return_value = 1;
			archive_entry_free(entry);
			continue;
		}

		/*
		 * Invoke archive_read_next_header2() to work
		 * archive_read_data_block(), which is called via write_file(),
		 * without failure.
		 */
		entry2 = archive_entry_new();
		r = archive_read_next_header2(disk, entry2);
		archive_entry_free(entry2);
		if (r != ARCHIVE_OK) {
			lafe_warnc(archive_errno(disk),
			    "%s", archive_error_string(disk));
			if (r == ARCHIVE_FATAL)
				bsdtar->return_value = 1;
			else
				archive_read_close(disk);
			archive_entry_free(entry);
			continue;
		}

		write_file(bsdtar, a, entry);
		archive_entry_free(entry);
		archive_read_close(disk);
		entry = NULL;
		archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry);
	}

	if (archive_write_close(a)) {
		lafe_warnc(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_free(bsdtar->diskreader);
	bsdtar->diskreader = NULL;

	if (bsdtar->option_totals) {
		fprintf(stderr, "Total bytes written: %s\n",
		    tar_i64toa(archive_filter_bytes(a, -1)));
	}

	archive_write_free(a);
}
Exemplo n.º 21
0
int
main(int argc, char *argv[])
{
	static char buff[16384];
	struct cpio _cpio; /* Allocated on stack. */
	struct cpio *cpio;
	const char *errmsg;
	int uid, gid;
	int opt;

	cpio = &_cpio;
	memset(cpio, 0, sizeof(*cpio));
	cpio->buff = buff;
	cpio->buff_size = sizeof(buff);

#if defined(HAVE_SIGACTION) && defined(SIGPIPE)
	{ /* Ignore SIGPIPE signals. */
		struct sigaction sa;
		sigemptyset(&sa.sa_mask);
		sa.sa_flags = 0;
		sa.sa_handler = SIG_IGN;
		sigaction(SIGPIPE, &sa, NULL);
	}
#endif

	/* Set lafe_progname before calling lafe_warnc. */
	lafe_setprogname(*argv, "bsdcpio");

#if HAVE_SETLOCALE
	if (setlocale(LC_ALL, "") == NULL)
		lafe_warnc(0, "Failed to set default locale");
#endif

	cpio->uid_override = -1;
	cpio->gid_override = -1;
	cpio->argv = argv;
	cpio->argc = argc;
	cpio->mode = '\0';
	cpio->verbose = 0;
	cpio->compress = '\0';
	cpio->extract_flags = ARCHIVE_EXTRACT_NO_AUTODIR;
	cpio->extract_flags |= ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER;
	cpio->extract_flags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS;
	cpio->extract_flags |= ARCHIVE_EXTRACT_SECURE_NODOTDOT;
	cpio->extract_flags |= ARCHIVE_EXTRACT_PERM;
	cpio->extract_flags |= ARCHIVE_EXTRACT_FFLAGS;
	cpio->extract_flags |= ARCHIVE_EXTRACT_ACL;
#if !defined(_WIN32) && !defined(__CYGWIN__)
	if (geteuid() == 0)
		cpio->extract_flags |= ARCHIVE_EXTRACT_OWNER;
#endif
	cpio->bytes_per_block = 512;
	cpio->filename = NULL;

	cpio->matching = archive_match_new();
	if (cpio->matching == NULL)
		lafe_errc(1, 0, "Out of memory");

	while ((opt = cpio_getopt(cpio)) != -1) {
		switch (opt) {
		case '0': /* GNU convention: --null, -0 */
			cpio->option_null = 1;
			break;
		case 'A': /* NetBSD/OpenBSD */
			cpio->option_append = 1;
			break;
		case 'a': /* POSIX 1997 */
			cpio->option_atime_restore = 1;
			break;
		case 'B': /* POSIX 1997 */
			cpio->bytes_per_block = 5120;
			break;
		case OPTION_B64ENCODE:
			cpio->add_filter = opt;
			break;
		case 'C': /* NetBSD/OpenBSD */
			cpio->bytes_per_block = atoi(cpio->argument);
			if (cpio->bytes_per_block <= 0)
				lafe_errc(1, 0, "Invalid blocksize %s", cpio->argument);
			break;
		case 'c': /* POSIX 1997 */
			cpio->format = "odc";
			break;
		case 'd': /* POSIX 1997 */
			cpio->extract_flags &= ~ARCHIVE_EXTRACT_NO_AUTODIR;
			break;
		case 'E': /* NetBSD/OpenBSD */
			if (archive_match_include_pattern_from_file(
			    cpio->matching, cpio->argument,
			    cpio->option_null) != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(cpio->matching));
			break;
		case 'F': /* NetBSD/OpenBSD/GNU cpio */
			cpio->filename = cpio->argument;
			break;
		case 'f': /* POSIX 1997 */
			if (archive_match_exclude_pattern(cpio->matching,
			    cpio->argument) != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(cpio->matching));
			break;
		case OPTION_GRZIP:
			cpio->compress = opt;
			break;
		case 'H': /* GNU cpio (also --format) */
			cpio->format = cpio->argument;
			break;
		case 'h':
			long_help();
			break;
		case 'I': /* NetBSD/OpenBSD */
			cpio->filename = cpio->argument;
			break;
		case 'i': /* POSIX 1997 */
			if (cpio->mode != '\0')
				lafe_errc(1, 0,
				    "Cannot use both -i and -%c", cpio->mode);
			cpio->mode = opt;
			break;
		case 'J': /* GNU tar, others */
			cpio->compress = opt;
			break;
		case 'j': /* GNU tar, others */
			cpio->compress = opt;
			break;
		case OPTION_INSECURE:
			cpio->extract_flags &= ~ARCHIVE_EXTRACT_SECURE_SYMLINKS;
			cpio->extract_flags &= ~ARCHIVE_EXTRACT_SECURE_NODOTDOT;
			break;
		case 'L': /* GNU cpio */
			cpio->option_follow_links = 1;
			break;
		case 'l': /* POSIX 1997 */
			cpio->option_link = 1;
			break;
		case OPTION_LRZIP:
		case OPTION_LZMA: /* GNU tar, others */
		case OPTION_LZOP: /* GNU tar, others */
			cpio->compress = opt;
			break;
		case 'm': /* POSIX 1997 */
			cpio->extract_flags |= ARCHIVE_EXTRACT_TIME;
			break;
		case 'n': /* GNU cpio */
			cpio->option_numeric_uid_gid = 1;
			break;
		case OPTION_NO_PRESERVE_OWNER: /* GNU cpio */
			cpio->extract_flags &= ~ARCHIVE_EXTRACT_OWNER;
			break;
		case 'O': /* GNU cpio */
			cpio->filename = cpio->argument;
			break;
		case 'o': /* POSIX 1997 */
			if (cpio->mode != '\0')
				lafe_errc(1, 0,
				    "Cannot use both -o and -%c", cpio->mode);
			cpio->mode = opt;
			break;
		case 'p': /* POSIX 1997 */
			if (cpio->mode != '\0')
				lafe_errc(1, 0,
				    "Cannot use both -p and -%c", cpio->mode);
			cpio->mode = opt;
			cpio->extract_flags &= ~ARCHIVE_EXTRACT_SECURE_NODOTDOT;
			break;
		case OPTION_PRESERVE_OWNER:
			cpio->extract_flags |= ARCHIVE_EXTRACT_OWNER;
			break;
		case OPTION_QUIET: /* GNU cpio */
			cpio->quiet = 1;
			break;
		case 'R': /* GNU cpio, also --owner */
			/* TODO: owner_parse should return uname/gname
			 * also; use that to set [ug]name_override. */
			errmsg = owner_parse(cpio->argument, &uid, &gid);
			if (errmsg) {
				lafe_warnc(-1, "%s", errmsg);
				usage();
			}
			if (uid != -1) {
				cpio->uid_override = uid;
				cpio->uname_override = NULL;
			}
			if (gid != -1) {
				cpio->gid_override = gid;
				cpio->gname_override = NULL;
			}
			break;
		case 'r': /* POSIX 1997 */
			cpio->option_rename = 1;
			break;
		case 't': /* POSIX 1997 */
			cpio->option_list = 1;
			break;
		case 'u': /* POSIX 1997 */
			cpio->extract_flags
			    &= ~ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER;
			break;
		case OPTION_UUENCODE:
			cpio->add_filter = opt;
			break;
		case 'v': /* POSIX 1997 */
			cpio->verbose++;
			break;
		case 'V': /* GNU cpio */
			cpio->dot++;
			break;
		case OPTION_VERSION: /* GNU convention */
			version();
			break;
#if 0
	        /*
		 * cpio_getopt() handles -W specially, so it's not
		 * available here.
		 */
		case 'W': /* Obscure, but useful GNU convention. */
			break;
#endif
		case 'y': /* tar convention */
			cpio->compress = opt;
			break;
		case 'Z': /* tar convention */
			cpio->compress = opt;
			break;
		case 'z': /* tar convention */
			cpio->compress = opt;
			break;
		default:
			usage();
		}
	}

	/*
	 * Sanity-check args, error out on nonsensical combinations.
	 */
	/* -t implies -i if no mode was specified. */
	if (cpio->option_list && cpio->mode == '\0')
		cpio->mode = 'i';
	/* -t requires -i */
	if (cpio->option_list && cpio->mode != 'i')
		lafe_errc(1, 0, "Option -t requires -i");
	/* -n requires -it */
	if (cpio->option_numeric_uid_gid && !cpio->option_list)
		lafe_errc(1, 0, "Option -n requires -it");
	/* Can only specify format when writing */
	if (cpio->format != NULL && cpio->mode != 'o')
		lafe_errc(1, 0, "Option --format requires -o");
	/* -l requires -p */
	if (cpio->option_link && cpio->mode != 'p')
		lafe_errc(1, 0, "Option -l requires -p");
	/* -v overrides -V */
	if (cpio->dot && cpio->verbose)
		cpio->dot = 0;
	/* TODO: Flag other nonsensical combinations. */

	switch (cpio->mode) {
	case 'o':
		/* TODO: Implement old binary format in libarchive,
		   use that here. */
		if (cpio->format == NULL)
			cpio->format = "odc"; /* Default format */

		mode_out(cpio);
		break;
	case 'i':
		while (*cpio->argv != NULL) {
			if (archive_match_include_pattern(cpio->matching,
			    *cpio->argv) != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(cpio->matching));
			--cpio->argc;
			++cpio->argv;
		}
		if (cpio->option_list)
			mode_list(cpio);
		else
			mode_in(cpio);
		break;
	case 'p':
		if (*cpio->argv == NULL || **cpio->argv == '\0')
			lafe_errc(1, 0,
			    "-p mode requires a target directory");
		mode_pass(cpio, *cpio->argv);
		break;
	default:
		lafe_errc(1, 0,
		    "Must specify at least one of -i, -o, or -p");
	}

	archive_match_free(cpio->matching);
	free_cache(cpio->gname_cache);
	free_cache(cpio->uname_cache);
	free(cpio->destdir);
	return (cpio->return_value);
}
Exemplo n.º 22
0
/*
 * 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) {
        lafe_warnc(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) {
            lafe_warnc(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) {
            lafe_warnc(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);
        }
    }

    /* By default, don't write or restore absolute pathnames. */
    if (!bsdtar->option_absolute_paths) {
        const char *rp, *p = name;
        int slashonly = 1;

        /* Remove leading "//./" or "//?/" or "//?/UNC/"
         * (absolute path prefixes used by Windows API) */
        if ((p[0] == '/' || p[0] == '\\') &&
                (p[1] == '/' || p[1] == '\\') &&
                (p[2] == '.' || p[2] == '?') &&
                (p[3] == '/' || p[3] == '\\'))
        {
            if (p[2] == '?' &&
                    (p[4] == 'U' || p[4] == 'u') &&
                    (p[5] == 'N' || p[5] == 'n') &&
                    (p[6] == 'C' || p[6] == 'c') &&
                    (p[7] == '/' || p[7] == '\\'))
                p += 8;
            else
                p += 4;
            slashonly = 0;
        }
        do {
            rp = p;
            /* Remove leading drive letter from archives created
             * on Windows. */
            if (((p[0] >= 'a' && p[0] <= 'z') ||
                    (p[0] >= 'A' && p[0] <= 'Z')) &&
                    p[1] == ':') {
                p += 2;
                slashonly = 0;
            }
            /* Remove leading "/../", "//", etc. */
            while (p[0] == '/' || p[0] == '\\') {
                if (p[1] == '.' && p[2] == '.' &&
                        (p[3] == '/' || p[3] == '\\')) {
                    p += 3; /* Remove "/..", leave "/"
                             * for next pass. */
                    slashonly = 0;
                } else
                    p += 1; /* Remove "/". */
            }
        } while (rp != p);

        if (p != name && !bsdtar->warned_lead_slash) {
            /* Generate a warning the first time this happens. */
            if (slashonly)
                lafe_warnc(0,
                           "Removing leading '%c' from member names",
                           name[0]);
            else
                lafe_warnc(0,
                           "Removing leading drive letter from "
                           "member names");
            bsdtar->warned_lead_slash = 1;
        }

        /* Special case: Stripping everything yields ".". */
        if (*p == '\0')
            name = ".";
        else
            name = p;
    } else {
        /* Strip redundant leading '/' characters. */
        while (name[0] == '/' && name[1] == '/')
            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);
}
Exemplo n.º 23
0
static int
entry_to_archive(struct cpio *cpio, struct archive_entry *entry)
{
	const char *destpath = archive_entry_pathname(entry);
	const char *srcpath = archive_entry_sourcepath(entry);
	int fd = -1;
	ssize_t bytes_read;
	int r;

	/* Print out the destination name to the user. */
	if (cpio->verbose)
		fprintf(stderr,"%s", destpath);

	/*
	 * Option_link only makes sense in pass mode and for
	 * regular files.  Also note: if a link operation fails
	 * because of cross-device restrictions, we'll fall back
	 * to copy mode for that entry.
	 *
	 * TODO: Test other cpio implementations to see if they
	 * hard-link anything other than regular files here.
	 */
	if (cpio->option_link
	    && archive_entry_filetype(entry) == AE_IFREG)
	{
		struct archive_entry *t;
		/* Save the original entry in case we need it later. */
		t = archive_entry_clone(entry);
		if (t == NULL)
			lafe_errc(1, ENOMEM, "Can't create link");
		/* Note: link(2) doesn't create parent directories,
		 * so we use archive_write_header() instead as a
		 * convenience. */
		archive_entry_set_hardlink(t, srcpath);
		/* This is a straight link that carries no data. */
		archive_entry_set_size(t, 0);
		r = archive_write_header(cpio->archive, t);
		archive_entry_free(t);
		if (r != ARCHIVE_OK)
			lafe_warnc(archive_errno(cpio->archive),
			    "%s", archive_error_string(cpio->archive));
		if (r == ARCHIVE_FATAL)
			exit(1);
#ifdef EXDEV
		if (r != ARCHIVE_OK && archive_errno(cpio->archive) == EXDEV) {
			/* Cross-device link:  Just fall through and use
			 * the original entry to copy the file over. */
			lafe_warnc(0, "Copying file instead");
		} else
#endif
		return (0);
	}

	/*
	 * Make sure we can open the file (if necessary) before
	 * trying to write the header.
	 */
	if (archive_entry_filetype(entry) == AE_IFREG) {
		if (archive_entry_size(entry) > 0) {
			fd = open(srcpath, O_RDONLY | O_BINARY);
			if (fd < 0) {
				lafe_warnc(errno,
				    "%s: could not open file", srcpath);
				goto cleanup;
			}
		}
	} else {
		archive_entry_set_size(entry, 0);
	}

	r = archive_write_header(cpio->archive, entry);

	if (r != ARCHIVE_OK)
		lafe_warnc(archive_errno(cpio->archive),
		    "%s: %s",
		    srcpath,
		    archive_error_string(cpio->archive));

	if (r == ARCHIVE_FATAL)
		exit(1);

	if (r >= ARCHIVE_WARN && fd >= 0) {
		bytes_read = read(fd, cpio->buff, cpio->buff_size);
		while (bytes_read > 0) {
			r = archive_write_data(cpio->archive,
			    cpio->buff, bytes_read);
			if (r < 0)
				lafe_errc(1, archive_errno(cpio->archive),
				    "%s", archive_error_string(cpio->archive));
			if (r < bytes_read) {
				lafe_warnc(0,
				    "Truncated write; file may have grown while being archived.");
			}
			bytes_read = read(fd, cpio->buff, cpio->buff_size);
		}
	}

	fd = restore_time(cpio, entry, srcpath, fd);

cleanup:
	if (cpio->verbose)
		fprintf(stderr,"\n");
	if (fd >= 0)
		close(fd);
	return (0);
}
Exemplo n.º 24
0
/*
 * This is used by both out mode (to copy objects from disk into
 * an archive) and pass mode (to copy objects from disk to
 * an archive_write_disk "archive").
 */
static int
file_to_archive(struct cpio *cpio, const char *srcpath)
{
	const char *destpath;
	struct archive_entry *entry, *spare;
	size_t len;
	const char *p;
	int r;

	/*
	 * Create an archive_entry describing the source file.
	 *
	 */
	entry = archive_entry_new();
	if (entry == NULL)
		lafe_errc(1, 0, "Couldn't allocate entry");
	archive_entry_copy_sourcepath(entry, srcpath);
	r = archive_read_disk_entry_from_file(cpio->archive_read_disk,
	    entry, -1, NULL);
	if (r < ARCHIVE_FAILED)
		lafe_errc(1, 0, "%s",
		    archive_error_string(cpio->archive_read_disk));
	if (r < ARCHIVE_OK)
		lafe_warnc(0, "%s",
		    archive_error_string(cpio->archive_read_disk));
	if (r <= ARCHIVE_FAILED) {
		cpio->return_value = 1;
		return (r);
	}

	if (cpio->uid_override >= 0)
		archive_entry_set_uid(entry, cpio->uid_override);
	if (cpio->gid_override >= 0)
		archive_entry_set_gid(entry, cpio->gid_override);

	/*
	 * Generate a destination path for this entry.
	 * "destination path" is the name to which it will be copied in
	 * pass mode or the name that will go into the archive in
	 * output mode.
	 */
	destpath = srcpath;
	if (cpio->destdir) {
		len = strlen(cpio->destdir) + strlen(srcpath) + 8;
		if (len >= cpio->pass_destpath_alloc) {
			while (len >= cpio->pass_destpath_alloc) {
				cpio->pass_destpath_alloc += 512;
				cpio->pass_destpath_alloc *= 2;
			}
			free(cpio->pass_destpath);
			cpio->pass_destpath = malloc(cpio->pass_destpath_alloc);
			if (cpio->pass_destpath == NULL)
				lafe_errc(1, ENOMEM,
				    "Can't allocate path buffer");
		}
		strcpy(cpio->pass_destpath, cpio->destdir);
		p = srcpath;
		while (p[0] == '/')
			++p;
		strcat(cpio->pass_destpath, p);
		destpath = cpio->pass_destpath;
	}
	if (cpio->option_rename)
		destpath = cpio_rename(destpath);
	if (destpath == NULL)
		return (0);
	archive_entry_copy_pathname(entry, destpath);

	/*
	 * If we're trying to preserve hardlinks, match them here.
	 */
	spare = NULL;
	if (cpio->linkresolver != NULL
	    && archive_entry_filetype(entry) != AE_IFDIR) {
		archive_entry_linkify(cpio->linkresolver, &entry, &spare);
	}

	if (entry != NULL) {
		r = entry_to_archive(cpio, entry);
		archive_entry_free(entry);
		if (spare != NULL) {
			if (r == 0)
				r = entry_to_archive(cpio, spare);
			archive_entry_free(spare);
		}
	}
	return (r);
}
Exemplo n.º 25
0
/*
 * 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;

	/* Choose a suitable copy buffer size */
	bsdtar->buff_size = 64 * 1024;
	while (bsdtar->buff_size < (size_t)bsdtar->bytes_per_block)
	  bsdtar->buff_size *= 2;
	/* Try to compensate for space we'll lose to alignment. */
	bsdtar->buff_size += 16 * 1024;

	/* Allocate a buffer for file data. */
	if ((bsdtar->buff = malloc(bsdtar->buff_size)) == NULL)
		lafe_errc(1, 0, "cannot allocate memory");

	if ((bsdtar->resolver = archive_entry_linkresolver_new()) == NULL)
		lafe_errc(1, 0, "cannot create link resolver");
	archive_entry_linkresolver_set_strategy(bsdtar->resolver,
	    archive_format(a));
	if ((bsdtar->diskreader = archive_read_disk_new()) == NULL)
		lafe_errc(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) {
					lafe_warnc(0, "%s",
					    "Missing argument for -C");
					bsdtar->return_value = 1;
					goto cleanup;
				}
				if (*arg == '\0') {
					lafe_warnc(0,
					    "Meaningless 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
				write_hierarchy(bsdtar, a, arg);
		}
		bsdtar->argv++;
	}

	entry = NULL;
	archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry);
	while (entry != NULL) {
		write_file(bsdtar, a, entry);
		archive_entry_free(entry);
		entry = NULL;
		archive_entry_linkify(bsdtar->resolver, &entry, &sparse_entry);
	}

	if (archive_write_close(a)) {
		lafe_warnc(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_free(bsdtar->diskreader);
	bsdtar->diskreader = NULL;

	if (bsdtar->option_totals) {
		fprintf(stderr, "Total bytes written: %s\n",
		    tar_i64toa(archive_position_compressed(a)));
	}

	archive_write_free(a);
}
Exemplo n.º 26
0
int
main(int argc, char **argv)
{
	struct bsdtar		*bsdtar, bsdtar_storage;
	int			 opt, t;
	char			 compression, compression2;
	const char		*compression_name, *compression2_name;
	const char		*compress_program;
	char			 option_a, 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" */
	bsdtar->gid = -1;
	bsdtar->uid = -1;
	option_a = option_o = 0;
	compression = compression2 = '\0';
	compression_name = compression2_name = NULL;
	compress_program = NULL;

#if defined(HAVE_SIGACTION)
	{ /* Set up signal handling. */
		struct sigaction sa;
		sa.sa_handler = siginfo_handler;
		sigemptyset(&sa.sa_mask);
		sa.sa_flags = 0;
#ifdef SIGINFO
		if (sigaction(SIGINFO, &sa, NULL))
			lafe_errc(1, errno, "sigaction(SIGINFO) failed");
#endif
#ifdef SIGUSR1
		/* ... and treat SIGUSR1 the same way as SIGINFO. */
		if (sigaction(SIGUSR1, &sa, NULL))
			lafe_errc(1, errno, "sigaction(SIGUSR1) failed");
#endif
#ifdef SIGPIPE
		/* Ignore SIGPIPE signals. */
		sa.sa_handler = SIG_IGN;
		sigaction(SIGPIPE, &sa, NULL);
#endif
	}
#endif

	/* Set lafe_progname before calling lafe_warnc. */
	lafe_setprogname(*argv, "bsdtar");

#if HAVE_SETLOCALE
	if (setlocale(LC_ALL, "") == NULL)
		lafe_warnc(0, "Failed to set default locale");
#endif
#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 block size settings. */
	bsdtar->bytes_per_block = DEFAULT_BYTES_PER_BLOCK;
	/* Allow library to default this unless user specifies -b. */
	bsdtar->bytes_in_last_block = -1;

	/* Default: preserve mod time on extract */
	bsdtar->extract_flags = ARCHIVE_EXTRACT_TIME;

	/* Default: Perform basic security checks. */
	bsdtar->extract_flags |= SECURITY;

#ifndef _WIN32
	/* On POSIX systems, assume --same-owner and -p when run by
	 * the root user.  This doesn't make any sense on Windows. */
	if (bsdtar->user_uid == 0) {
		/* --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;
		bsdtar->extract_flags |= ARCHIVE_EXTRACT_MAC_METADATA;
	}
#endif

	/*
	 * Enable Mac OS "copyfile()" extension by default.
	 * This has no effect on other platforms.
	 */
	bsdtar->readdisk_flags |= ARCHIVE_READDISK_MAC_COPYFILE;
#ifdef COPYFILE_DISABLE_VAR
	if (getenv(COPYFILE_DISABLE_VAR))
		bsdtar->readdisk_flags &= ~ARCHIVE_READDISK_MAC_COPYFILE;
#endif
	bsdtar->matching = archive_match_new();
	if (bsdtar->matching == NULL)
		lafe_errc(1, errno, "Out of memory");
	bsdtar->cset = cset_new();
	if (bsdtar->cset == NULL)
		lafe_errc(1, errno, "Out of memory");

	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 'a': /* GNU tar */
			option_a = 1; /* Record it and resolve it later. */
			break;
		case 'B': /* GNU tar */
			/* libarchive doesn't need this; just ignore it. */
			break;
		case 'b': /* SUSv2 */
			t = atoi(bsdtar->argument);
			if (t <= 0 || t > 8192)
				lafe_errc(1, 0,
				    "Argument to -b is out of range (1..8192)");
			bsdtar->bytes_per_block = 512 * t;
			/* Explicit -b forces last block size. */
			bsdtar->bytes_in_last_block = bsdtar->bytes_per_block;
			break;
		case OPTION_B64ENCODE:
			if (compression2 != '\0')
				lafe_errc(1, 0,
				    "Can't specify both --uuencode and "
				    "--b64encode");
			compression2 = opt;
			compression2_name = "b64encode";
			break;
		case 'C': /* GNU tar */
			if (strlen(bsdtar->argument) == 0)
				lafe_errc(1, 0,
				    "Meaningless option: -C ''");

			set_chdir(bsdtar, bsdtar->argument);
			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_DISABLE_COPYFILE: /* Mac OS X */
			bsdtar->readdisk_flags &= ~ARCHIVE_READDISK_MAC_COPYFILE;
			break;
		case OPTION_EXCLUDE: /* GNU tar */
			if (archive_match_exclude_pattern(
			    bsdtar->matching, bsdtar->argument) != ARCHIVE_OK)
				lafe_errc(1, 0,
				    "Couldn't exclude %s\n", bsdtar->argument);
			break;
		case OPTION_FORMAT: /* GNU tar, others */
			cset_set_format(bsdtar->cset, bsdtar->argument);
			break;
		case 'f': /* SUSv2 */
			bsdtar->filename = bsdtar->argument;
			break;
		case OPTION_GID: /* cpio */
			t = atoi(bsdtar->argument);
			if (t < 0)
				lafe_errc(1, 0,
				    "Argument to --gid must be positive");
			bsdtar->gid = t;
			break;
		case OPTION_GNAME: /* cpio */
			bsdtar->gname = bsdtar->argument;
			break;
		case OPTION_GRZIP:
			if (compression != '\0')
				lafe_errc(1, 0,
				    "Can't specify both -%c and -%c", opt,
				    compression);
			compression = opt;
			compression_name = "grzip";
			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();
			exit(0);
			break;
		case OPTION_HFS_COMPRESSION: /* Mac OS X v10.6 or later */
			bsdtar->extract_flags |=
			    ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED;
			break;
		case OPTION_IGNORE_ZEROS:
			bsdtar->option_ignore_zeros = 1;
			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->argument;
			break;
		case OPTION_INCLUDE:
			/*
			 * No one else has the @archive extension, so
			 * no one else needs this to filter entries
			 * when transforming archives.
			 */
			if (archive_match_include_pattern(bsdtar->matching,
			    bsdtar->argument) != ARCHIVE_OK)
				lafe_errc(1, 0,
				    "Failed to add %s to inclusion list",
				    bsdtar->argument);
			break;
		case 'j': /* GNU tar */
			if (compression != '\0')
				lafe_errc(1, 0,
				    "Can't specify both -%c and -%c", opt,
				    compression);
			compression = opt;
			compression_name = "bzip2";
			break;
		case 'J': /* GNU tar 1.21 and later */
			if (compression != '\0')
				lafe_errc(1, 0,
				    "Can't specify both -%c and -%c", opt,
				    compression);
			compression = opt;
			compression_name = "xz";
			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 OPTION_LRZIP:
		case OPTION_LZ4:
		case OPTION_LZIP: /* GNU tar beginning with 1.23 */
		case OPTION_LZMA: /* GNU tar beginning with 1.20 */
		case OPTION_LZOP: /* GNU tar beginning with 1.21 */
			if (compression != '\0')
				lafe_errc(1, 0,
				    "Can't specify both -%c and -%c", opt,
				    compression);
			compression = opt;
			switch (opt) {
			case OPTION_LRZIP: compression_name = "lrzip"; break;
			case OPTION_LZ4:  compression_name = "lz4"; break;
			case OPTION_LZIP: compression_name = "lzip"; break; 
			case OPTION_LZMA: compression_name = "lzma"; break; 
			case OPTION_LZOP: compression_name = "lzop"; break; 
			}
			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)
		 */
		case OPTION_NEWER_CTIME: /* GNU tar */
			if (archive_match_include_date(bsdtar->matching,
			    ARCHIVE_MATCH_CTIME | ARCHIVE_MATCH_NEWER,
			    bsdtar->argument) != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(bsdtar->matching));
			break;
		case OPTION_NEWER_CTIME_THAN:
			if (archive_match_include_file_time(bsdtar->matching,
			    ARCHIVE_MATCH_CTIME | ARCHIVE_MATCH_NEWER,
			    bsdtar->argument) != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(bsdtar->matching));
			break;
		case OPTION_NEWER_MTIME: /* GNU tar */
			if (archive_match_include_date(bsdtar->matching,
			    ARCHIVE_MATCH_MTIME | ARCHIVE_MATCH_NEWER,
			    bsdtar->argument) != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(bsdtar->matching));
			break;
		case OPTION_NEWER_MTIME_THAN:
			if (archive_match_include_file_time(bsdtar->matching,
			    ARCHIVE_MATCH_MTIME | ARCHIVE_MATCH_NEWER,
			    bsdtar->argument) != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(bsdtar->matching));
			break;
		case OPTION_NODUMP: /* star */
			bsdtar->readdisk_flags |= ARCHIVE_READDISK_HONOR_NODUMP;
			break;
		case OPTION_NOPRESERVE_HFS_COMPRESSION:
			/* Mac OS X v10.6 or later */
			bsdtar->extract_flags |=
			    ARCHIVE_EXTRACT_NO_HFS_COMPRESSION;
			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;
			bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_MAC_METADATA;
			break;
		case OPTION_NULL: /* GNU tar */
			bsdtar->option_null++;
			break;
		case OPTION_NUMERIC_OWNER: /* GNU tar */
			bsdtar->uname = "";
			bsdtar->gname = "";
			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;
	        /*
		 * Selecting files by time:
		 *    --older-?time='date' Only files older than 'date'
		 *    --older-?time-than='file' Only files older than time
		 *         on specified file
		 */
		case OPTION_OLDER_CTIME:
			if (archive_match_include_date(bsdtar->matching,
			    ARCHIVE_MATCH_CTIME | ARCHIVE_MATCH_OLDER,
			    bsdtar->argument) != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(bsdtar->matching));
			break;
		case OPTION_OLDER_CTIME_THAN:
			if (archive_match_include_file_time(bsdtar->matching,
			    ARCHIVE_MATCH_CTIME | ARCHIVE_MATCH_OLDER,
			    bsdtar->argument) != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(bsdtar->matching));
			break;
		case OPTION_OLDER_MTIME:
			if (archive_match_include_date(bsdtar->matching,
			    ARCHIVE_MATCH_MTIME | ARCHIVE_MATCH_OLDER,
			    bsdtar->argument) != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(bsdtar->matching));
			break;
		case OPTION_OLDER_MTIME_THAN:
			if (archive_match_include_file_time(bsdtar->matching,
			    ARCHIVE_MATCH_MTIME | ARCHIVE_MATCH_OLDER,
			    bsdtar->argument) != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(bsdtar->matching));
			break;
		case OPTION_ONE_FILE_SYSTEM: /* GNU tar */
			bsdtar->readdisk_flags |=
			    ARCHIVE_READDISK_NO_TRAVERSE_MOUNTS;
			break;
		case OPTION_OPTIONS:
			bsdtar->option_options = bsdtar->argument;
			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;
			bsdtar->extract_flags |= ARCHIVE_EXTRACT_MAC_METADATA;
			break;
		case OPTION_PASSPHRASE:
			bsdtar->passphrase = bsdtar->argument;
			break;
		case OPTION_POSIX: /* GNU tar */
			cset_set_format(bsdtar->cset, "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 defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H)
			add_substitution(bsdtar, bsdtar->argument);
#else
			lafe_warnc(0,
			    "-s is not supported by this version of bsdtar");
			usage();
#endif
			break;
		case OPTION_SAME_OWNER: /* GNU tar */
			bsdtar->extract_flags |= ARCHIVE_EXTRACT_OWNER;
			break;
		case OPTION_STRIP_COMPONENTS: /* GNU tar 1.15 */
			errno = 0;
			bsdtar->strip_components = strtol(bsdtar->argument,
			    NULL, 0);
			if (errno)
				lafe_errc(1, 0,
				    "Invalid --strip-components argument: %s",
				    bsdtar->argument);
			break;
		case 'T': /* GNU tar */
			bsdtar->names_from_file = bsdtar->argument;
			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 OPTION_UID: /* cpio */
			t = atoi(bsdtar->argument);
			if (t < 0)
				lafe_errc(1, 0,
				    "Argument to --uid must be positive");
			bsdtar->uid = t;
			break;
		case OPTION_UNAME: /* cpio */
			bsdtar->uname = bsdtar->argument;
			break;
		case OPTION_UUENCODE:
			if (compression2 != '\0')
				lafe_errc(1, 0,
				    "Can't specify both --uuencode and "
				    "--b64encode");
			compression2 = opt;
			compression2_name = "uuencode";
			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 (archive_match_exclude_pattern_from_file(
			    bsdtar->matching, bsdtar->argument, 0)
			    != ARCHIVE_OK)
				lafe_errc(1, 0, "Error : %s",
				    archive_error_string(bsdtar->matching));
			break;
		case 'x': /* SUSv2 */
			set_mode(bsdtar, opt);
			break;
		case 'y': /* FreeBSD version of GNU tar */
			if (compression != '\0')
				lafe_errc(1, 0,
				    "Can't specify both -%c and -%c", opt,
				    compression);
			compression = opt;
			compression_name = "bzip2";
			break;
		case 'Z': /* GNU tar */
			if (compression != '\0')
				lafe_errc(1, 0,
				    "Can't specify both -%c and -%c", opt,
				    compression);
			compression = opt;
			compression_name = "compress";
			break;
		case 'z': /* GNU tar, star, many others */
			if (compression != '\0')
				lafe_errc(1, 0,
				    "Can't specify both -%c and -%c", opt,
				    compression);
			compression = opt;
			compression_name = "gzip";
			break;
		case OPTION_USE_COMPRESS_PROGRAM:
			compress_program = bsdtar->argument;
			break;
		default:
			usage();
		}
	}

	/*
	 * Sanity-check options.
	 */

	/* If no "real" mode was specified, treat -h as --help. */
	if ((bsdtar->mode == '\0') && possible_help_request) {
		long_help();
		exit(0);
	}

	/* Otherwise, a mode is required. */
	if (bsdtar->mode == '\0')
		lafe_errc(1, 0,
		    "Must specify one of -c, -r, -t, -u, -x");

	/* Check boolean options only permitted in certain modes. */
	if (option_a)
		only_mode(bsdtar, "-a", "c");
	if (bsdtar->readdisk_flags & ARCHIVE_READDISK_NO_TRAVERSE_MOUNTS)
		only_mode(bsdtar, "--one-file-system", "cru");
	if (bsdtar->option_fast_read)
		only_mode(bsdtar, "--fast-read", "xt");
	if (bsdtar->extract_flags & ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED)
		only_mode(bsdtar, "--hfsCompression", "x");
	if (bsdtar->extract_flags & ARCHIVE_EXTRACT_NO_HFS_COMPRESSION)
		only_mode(bsdtar, "--nopreserveHFSCompression", "x");
	if (bsdtar->readdisk_flags & ARCHIVE_READDISK_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.
			 */
			cset_set_format(bsdtar->cset, "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");

	if (option_a && cset_auto_compress(bsdtar->cset, bsdtar->filename)) {
		/* Ignore specified compressions if auto-compress works. */
		compression = '\0';
		compression2 = '\0';
	}
	/* Check other parameters only permitted in certain modes. */
	if (compress_program != NULL) {
		only_mode(bsdtar, "--use-compress-program", "cxt");
		cset_add_filter_program(bsdtar->cset, compress_program);
		/* Ignore specified compressions. */
		compression = '\0';
		compression2 = '\0';
	}
	if (compression != '\0') {
		switch (compression) {
		case 'J': case 'j': case 'y': case 'Z': case 'z':
			strcpy(buff, "-?");
			buff[1] = compression;
			break;
		default:
			strcpy(buff, "--");
			strcat(buff, compression_name);
			break;
		}
		only_mode(bsdtar, buff, "cxt");
		cset_add_filter(bsdtar->cset, compression_name);
	}
	if (compression2 != '\0') {
		strcpy(buff, "--");
		strcat(buff, compression2_name);
		only_mode(bsdtar, buff, "cxt");
		cset_add_filter(bsdtar->cset, compression2_name);
	}
	if (cset_get_format(bsdtar->cset) != NULL)
		only_mode(bsdtar, "--format", "cru");
	if (bsdtar->symlink_mode != '\0') {
		strcpy(buff, "-?");
		buff[1] = bsdtar->symlink_mode;
		only_mode(bsdtar, buff, "cru");
	}

	/* Filename "-" implies stdio. */
	if (strcmp(bsdtar->filename, "-") == 0)
		bsdtar->filename = NULL;

	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;
	}

	archive_match_free(bsdtar->matching);
#if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H)
	cleanup_substitution(bsdtar);
#endif
	cset_free(bsdtar->cset);
	passphrase_free(bsdtar->ppbuff);

	if (bsdtar->return_value != 0)
		lafe_warnc(0,
		    "Error exit delayed from previous errors.");
	return (bsdtar->return_value);
}
Exemplo n.º 27
0
/*
 * Handle 'x' and 't' modes.
 */
static void
read_archive(struct bsdtar *bsdtar, char mode)
{
	struct progress_data	progress_data;
	FILE			 *out;
	struct archive		 *a;
	struct archive_entry	 *entry;
	const struct stat	 *st;
	int			  r;

	while (*bsdtar->argv) {
		lafe_include(&bsdtar->matching, *bsdtar->argv);
		bsdtar->argv++;
	}

	if (bsdtar->names_from_file != NULL)
		lafe_include_from_file(&bsdtar->matching,
		    bsdtar->names_from_file, bsdtar->option_null);

	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_OK != archive_read_set_options(a, bsdtar->option_options))
		lafe_errc(1, 0, "%s", archive_error_string(a));
	if (archive_read_open_file(a, bsdtar->filename,
	    bsdtar->bytes_per_block != 0 ? bsdtar->bytes_per_block :
	    DEFAULT_BYTES_PER_BLOCK))
		lafe_errc(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. */
		progress_data.bsdtar = bsdtar;
		progress_data.archive = a;
		archive_read_extract_set_progress_callback(a, progress_func,
		    &progress_data);
	}

	if (mode == 'x' && bsdtar->option_chroot) {
#if HAVE_CHROOT
		if (chroot(".") != 0)
			lafe_errc(1, errno, "Can't chroot to \".\"");
#else
		lafe_errc(1, 0,
		    "chroot isn't supported on this platform");
#endif
	}

	for (;;) {
		/* Support --fast-read option */
		if (bsdtar->option_fast_read &&
		    lafe_unmatched_inclusions(bsdtar->matching) == 0)
			break;

		r = archive_read_next_header(a, &entry);
		progress_data.entry = entry;
		if (r == ARCHIVE_EOF)
			break;
		if (r < ARCHIVE_OK)
			lafe_warnc(0, "%s", archive_error_string(a));
		if (r <= ARCHIVE_WARN)
			bsdtar->return_value = 1;
		if (r == ARCHIVE_RETRY) {
			/* Retryable error: try again */
			lafe_warnc(0, "Retrying...");
			continue;
		}
		if (r == ARCHIVE_FATAL)
			break;

		if (bsdtar->uid >= 0) {
			archive_entry_set_uid(entry, bsdtar->uid);
			archive_entry_set_uname(entry, NULL);
		}
		if (bsdtar->gid >= 0) {
			archive_entry_set_gid(entry, bsdtar->gid);
			archive_entry_set_gname(entry, NULL);
		}
		if (bsdtar->uname)
			archive_entry_set_uname(entry, bsdtar->uname);
		if (bsdtar->gname)
			archive_entry_set_gname(entry, bsdtar->gname);

		/*
		 * 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 (lafe_excluded(bsdtar->matching, 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");
				lafe_warnc(0, "%s",
				    archive_error_string(a));
			}
			if (r == ARCHIVE_RETRY) {
				fprintf(out, "\n");
				lafe_warnc(0, "%s",
				    archive_error_string(a));
			}
			if (r == ARCHIVE_FATAL) {
				fprintf(out, "\n");
				lafe_warnc(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);
			}

			// TODO 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;
		}
	}


	r = archive_read_close(a);
	if (r != ARCHIVE_OK)
		lafe_warnc(0, "%s", archive_error_string(a));
	if (r <= ARCHIVE_WARN)
		bsdtar->return_value = 1;

	if (bsdtar->verbose > 2)
		fprintf(stdout, "Archive Format: %s,  Compression: %s\n",
		    archive_format_name(a), archive_compression_name(a));

	archive_read_finish(a);
}
Exemplo n.º 28
0
/*
 * Handle 'x' and 't' modes.
 */
static void
read_archive(struct bsdtar *bsdtar, char mode, struct archive *writer)
{
	struct progress_data	progress_data;
	FILE			 *out;
	struct archive		 *a;
	struct archive_entry	 *entry;
	const char		 *reader_options;
	int			  r;

	while (*bsdtar->argv) {
		if (archive_match_include_pattern(bsdtar->matching,
		    *bsdtar->argv) != ARCHIVE_OK)
			lafe_errc(1, 0, "Error inclusion pattern: %s",
			    archive_error_string(bsdtar->matching));
		bsdtar->argv++;
	}

	if (bsdtar->names_from_file != NULL)
		if (archive_match_include_pattern_from_file(
		    bsdtar->matching, bsdtar->names_from_file,
		    bsdtar->option_null) != ARCHIVE_OK)
			lafe_errc(1, 0, "Error inclusion pattern: %s",
			    archive_error_string(bsdtar->matching));

	a = archive_read_new();
	if (cset_read_support_filter_program(bsdtar->cset, a) == 0)
		archive_read_support_filter_all(a);
	archive_read_support_format_all(a);

	reader_options = getenv(ENV_READER_OPTIONS);
	if (reader_options != NULL) {
		char *p;
		/* Set default read options. */
		p = malloc(sizeof(IGNORE_WRONG_MODULE_NAME)
		    + strlen(reader_options) + 1);
		if (p == NULL)
			lafe_errc(1, errno, "Out of memory");
		/* Prepend magic code to ignore options for
		 * a format or  modules which are not added to
		 *  the archive read object. */
		strncpy(p, IGNORE_WRONG_MODULE_NAME,
		    sizeof(IGNORE_WRONG_MODULE_NAME) -1);
		strcpy(p + sizeof(IGNORE_WRONG_MODULE_NAME) -1, reader_options);
		r = archive_read_set_options(a, p);
		free(p);
		if (r == ARCHIVE_FATAL)
			lafe_errc(1, 0, "%s", archive_error_string(a));
		else
			archive_clear_error(a);
	}
	if (ARCHIVE_OK != archive_read_set_options(a, bsdtar->option_options))
		lafe_errc(1, 0, "%s", archive_error_string(a));
	if (archive_read_open_filename(a, bsdtar->filename,
					bsdtar->bytes_per_block))
		lafe_errc(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. */
		progress_data.bsdtar = bsdtar;
		progress_data.archive = a;
		archive_read_extract_set_progress_callback(a, progress_func,
		    &progress_data);
	}

	if (mode == 'x' && bsdtar->option_chroot) {
#if HAVE_CHROOT
		if (chroot(".") != 0)
			lafe_errc(1, errno, "Can't chroot to \".\"");
#else
		lafe_errc(1, 0,
		    "chroot isn't supported on this platform");
#endif
	}

	for (;;) {
		/* Support --fast-read option */
		if (bsdtar->option_fast_read &&
		    archive_match_path_unmatched_inclusions(bsdtar->matching) == 0)
			break;

		r = archive_read_next_header(a, &entry);
		progress_data.entry = entry;
		if (r == ARCHIVE_EOF)
			break;
		if (r < ARCHIVE_OK)
			lafe_warnc(0, "%s", archive_error_string(a));
		if (r <= ARCHIVE_WARN)
			bsdtar->return_value = 1;
		if (r == ARCHIVE_RETRY) {
			/* Retryable error: try again */
			lafe_warnc(0, "Retrying...");
			continue;
		}
		if (r == ARCHIVE_FATAL)
			break;

		if (bsdtar->uid >= 0) {
			archive_entry_set_uid(entry, bsdtar->uid);
			archive_entry_set_uname(entry, NULL);
		}
		if (bsdtar->gid >= 0) {
			archive_entry_set_gid(entry, bsdtar->gid);
			archive_entry_set_gname(entry, NULL);
		}
		if (bsdtar->uname)
			archive_entry_set_uname(entry, bsdtar->uname);
		if (bsdtar->gname)
			archive_entry_set_gname(entry, bsdtar->gname);

		/*
		 * 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 (archive_match_excluded(bsdtar->matching, 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");
				lafe_warnc(0, "%s",
				    archive_error_string(a));
			}
			if (r == ARCHIVE_RETRY) {
				fprintf(out, "\n");
				lafe_warnc(0, "%s",
				    archive_error_string(a));
			}
			if (r == ARCHIVE_FATAL) {
				fprintf(out, "\n");
				lafe_warnc(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);
			}

			/* TODO siginfo_printinfo(bsdtar, 0); */

			if (bsdtar->option_stdout)
				r = archive_read_data_into_fd(a, 1);
			else
				r = archive_read_extract2(a, entry, writer);
			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;
		}
	}


	r = archive_read_close(a);
	if (r != ARCHIVE_OK)
		lafe_warnc(0, "%s", archive_error_string(a));
	if (r <= ARCHIVE_WARN)
		bsdtar->return_value = 1;

	if (bsdtar->verbose > 2)
		fprintf(stdout, "Archive Format: %s,  Compression: %s\n",
		    archive_format_name(a), archive_filter_name(a, 0));

	archive_read_free(a);
}
Exemplo n.º 29
0
int
bsdtar_getopt(struct bsdtar *bsdtar)
{
	enum { state_start = 0, state_old_tar, state_next_word,
	       state_short, state_long };

	const struct bsdtar_option *popt, *match = NULL, *match2 = NULL;
	const char *p, *long_prefix = "--";
	size_t optlength;
	int opt = '?';
	int required = 0;

	bsdtar->argument = NULL;

	/* First time through, initialize everything. */
	if (bsdtar->getopt_state == state_start) {
		/* Skip program name. */
		++bsdtar->argv;
		--bsdtar->argc;
		if (*bsdtar->argv == NULL)
			return (-1);
		/* Decide between "new style" and "old style" arguments. */
		if (bsdtar->argv[0][0] == '-') {
			bsdtar->getopt_state = state_next_word;
		} else {
			bsdtar->getopt_state = state_old_tar;
			bsdtar->getopt_word = *bsdtar->argv++;
			--bsdtar->argc;
		}
	}

	/*
	 * We're parsing old-style tar arguments
	 */
	if (bsdtar->getopt_state == state_old_tar) {
		/* Get the next option character. */
		opt = *bsdtar->getopt_word++;
		if (opt == '\0') {
			/* New-style args can follow old-style. */
			bsdtar->getopt_state = state_next_word;
		} else {
			/* See if it takes an argument. */
			p = strchr(short_options, opt);
			if (p == NULL)
				return ('?');
			if (p[1] == ':') {
				bsdtar->argument = *bsdtar->argv;
				if (bsdtar->argument == NULL) {
					lafe_warnc(0,
					    "Option %c requires an argument",
					    opt);
					return ('?');
				}
				++bsdtar->argv;
				--bsdtar->argc;
			}
		}
	}

	/*
	 * We're ready to look at the next word in argv.
	 */
	if (bsdtar->getopt_state == state_next_word) {
		/* No more arguments, so no more options. */
		if (bsdtar->argv[0] == NULL)
			return (-1);
		/* Doesn't start with '-', so no more options. */
		if (bsdtar->argv[0][0] != '-')
			return (-1);
		/* "--" marks end of options; consume it and return. */
		if (strcmp(bsdtar->argv[0], "--") == 0) {
			++bsdtar->argv;
			--bsdtar->argc;
			return (-1);
		}
		/* Get next word for parsing. */
		bsdtar->getopt_word = *bsdtar->argv++;
		--bsdtar->argc;
		if (bsdtar->getopt_word[1] == '-') {
			/* Set up long option parser. */
			bsdtar->getopt_state = state_long;
			bsdtar->getopt_word += 2; /* Skip leading '--' */
		} else {
			/* Set up short option parser. */
			bsdtar->getopt_state = state_short;
			++bsdtar->getopt_word;  /* Skip leading '-' */
		}
	}

	/*
	 * We're parsing a group of POSIX-style single-character options.
	 */
	if (bsdtar->getopt_state == state_short) {
		/* Peel next option off of a group of short options. */
		opt = *bsdtar->getopt_word++;
		if (opt == '\0') {
			/* End of this group; recurse to get next option. */
			bsdtar->getopt_state = state_next_word;
			return bsdtar_getopt(bsdtar);
		}

		/* Does this option take an argument? */
		p = strchr(short_options, opt);
		if (p == NULL)
			return ('?');
		if (p[1] == ':')
			required = 1;

		/* If it takes an argument, parse that. */
		if (required) {
			/* If arg is run-in, bsdtar->getopt_word already points to it. */
			if (bsdtar->getopt_word[0] == '\0') {
				/* Otherwise, pick up the next word. */
				bsdtar->getopt_word = *bsdtar->argv;
				if (bsdtar->getopt_word == NULL) {
					lafe_warnc(0,
					    "Option -%c requires an argument",
					    opt);
					return ('?');
				}
				++bsdtar->argv;
				--bsdtar->argc;
			}
			if (opt == 'W') {
				bsdtar->getopt_state = state_long;
				long_prefix = "-W "; /* For clearer errors. */
			} else {
				bsdtar->getopt_state = state_next_word;
				bsdtar->argument = bsdtar->getopt_word;
			}
		}
	}

	/* We're reading a long option, including -W long=arg convention. */
	if (bsdtar->getopt_state == state_long) {
		/* After this long option, we'll be starting a new word. */
		bsdtar->getopt_state = state_next_word;

		/* Option name ends at '=' if there is one. */
		p = strchr(bsdtar->getopt_word, '=');
		if (p != NULL) {
			optlength = (size_t)(p - bsdtar->getopt_word);
			bsdtar->argument = (char *)(uintptr_t)(p + 1);
		} else {
			optlength = strlen(bsdtar->getopt_word);
		}

		/* Search the table for an unambiguous match. */
		for (popt = tar_longopts; popt->name != NULL; popt++) {
			/* Short-circuit if first chars don't match. */
			if (popt->name[0] != bsdtar->getopt_word[0])
				continue;
			/* If option is a prefix of name in table, record it.*/
			if (strncmp(bsdtar->getopt_word, popt->name, optlength) == 0) {
				match2 = match; /* Record up to two matches. */
				match = popt;
				/* If it's an exact match, we're done. */
				if (strlen(popt->name) == optlength) {
					match2 = NULL; /* Forget the others. */
					break;
				}
			}
		}

		/* Fail if there wasn't a unique match. */
		if (match == NULL) {
			lafe_warnc(0,
			    "Option %s%s is not supported",
			    long_prefix, bsdtar->getopt_word);
			return ('?');
		}
		if (match2 != NULL) {
			lafe_warnc(0,
			    "Ambiguous option %s%s (matches --%s and --%s)",
			    long_prefix, bsdtar->getopt_word, match->name, match2->name);
			return ('?');
		}

		/* We've found a unique match; does it need an argument? */
		if (match->required) {
			/* Argument required: get next word if necessary. */
			if (bsdtar->argument == NULL) {
				bsdtar->argument = *bsdtar->argv;
				if (bsdtar->argument == NULL) {
					lafe_warnc(0,
					    "Option %s%s requires an argument",
					    long_prefix, match->name);
					return ('?');
				}
				++bsdtar->argv;
				--bsdtar->argc;
			}
		} else {
			/* Argument forbidden: fail if there is one. */
			if (bsdtar->argument != NULL) {
				lafe_warnc(0,
				    "Option %s%s does not allow an argument",
				    long_prefix, match->name);
				return ('?');
			}
		}
		return (match->equivalent);
	}

	return (opt);
}
Exemplo n.º 30
0
int
main(int argc, char *argv[])
{
	static char buff[16384];
	struct cpio _cpio; /* Allocated on stack. */
	struct cpio *cpio;
	const char *errmsg;
	int uid, gid;
	int opt;

	cpio = &_cpio;
	memset(cpio, 0, sizeof(*cpio));
	cpio->buff = buff;
	cpio->buff_size = sizeof(buff);

	/* Need lafe_progname before calling lafe_warnc. */
	if (*argv == NULL)
		lafe_progname = "bsdcpio";
	else {
#if defined(_WIN32) && !defined(__CYGWIN__)
		lafe_progname = strrchr(*argv, '\\');
#else
		lafe_progname = strrchr(*argv, '/');
#endif
		if (lafe_progname != NULL)
			lafe_progname++;
		else
			lafe_progname = *argv;
	}

	cpio->uid_override = -1;
	cpio->gid_override = -1;
	cpio->argv = argv;
	cpio->argc = argc;
	cpio->mode = '\0';
	cpio->verbose = 0;
	cpio->compress = '\0';
	cpio->extract_flags = ARCHIVE_EXTRACT_NO_AUTODIR;
	cpio->extract_flags |= ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER;
	cpio->extract_flags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS;
	cpio->extract_flags |= ARCHIVE_EXTRACT_SECURE_NODOTDOT;
	cpio->extract_flags |= ARCHIVE_EXTRACT_PERM;
	cpio->extract_flags |= ARCHIVE_EXTRACT_FFLAGS;
	cpio->extract_flags |= ARCHIVE_EXTRACT_ACL;
#if !defined(_WIN32) && !defined(__CYGWIN__)
	if (geteuid() == 0)
		cpio->extract_flags |= ARCHIVE_EXTRACT_OWNER;
#endif
	cpio->bytes_per_block = 512;
	cpio->filename = NULL;

	while ((opt = cpio_getopt(cpio)) != -1) {
		switch (opt) {
		case '0': /* GNU convention: --null, -0 */
			cpio->option_null = 1;
			break;
		case 'A': /* NetBSD/OpenBSD */
			cpio->option_append = 1;
			break;
		case 'a': /* POSIX 1997 */
			cpio->option_atime_restore = 1;
			break;
		case 'B': /* POSIX 1997 */
			cpio->bytes_per_block = 5120;
			break;
		case 'C': /* NetBSD/OpenBSD */
			cpio->bytes_per_block = atoi(cpio->optarg);
			if (cpio->bytes_per_block <= 0)
				lafe_errc(1, 0, "Invalid blocksize %s", cpio->optarg);
			break;
		case 'c': /* POSIX 1997 */
			cpio->format = "odc";
			break;
		case 'd': /* POSIX 1997 */
			cpio->extract_flags &= ~ARCHIVE_EXTRACT_NO_AUTODIR;
			break;
		case 'E': /* NetBSD/OpenBSD */
			lafe_include_from_file(&cpio->matching,
			    cpio->optarg, cpio->option_null);
			break;
		case 'F': /* NetBSD/OpenBSD/GNU cpio */
			cpio->filename = cpio->optarg;
			break;
		case 'f': /* POSIX 1997 */
			lafe_exclude(&cpio->matching, cpio->optarg);
			break;
		case 'H': /* GNU cpio (also --format) */
			cpio->format = cpio->optarg;
			break;
		case 'h':
			long_help();
			break;
		case 'I': /* NetBSD/OpenBSD */
			cpio->filename = cpio->optarg;
			break;
		case 'i': /* POSIX 1997 */
			if (cpio->mode != '\0')
				lafe_errc(1, 0,
				    "Cannot use both -i and -%c", cpio->mode);
			cpio->mode = opt;
			break;
		case 'J': /* GNU tar, others */
			cpio->compress = opt;
			break;
		case 'j': /* GNU tar, others */
			cpio->compress = opt;
			break;
		case OPTION_INSECURE:
			cpio->extract_flags &= ~ARCHIVE_EXTRACT_SECURE_SYMLINKS;
			cpio->extract_flags &= ~ARCHIVE_EXTRACT_SECURE_NODOTDOT;
			break;
		case 'L': /* GNU cpio */
			cpio->option_follow_links = 1;
			break;
		case 'l': /* POSIX 1997 */
			cpio->option_link = 1;
			break;
		case OPTION_LZMA: /* GNU tar, others */
			cpio->compress = opt;
			break;
		case 'm': /* POSIX 1997 */
			cpio->extract_flags |= ARCHIVE_EXTRACT_TIME;
			break;
		case 'n': /* GNU cpio */
			cpio->option_numeric_uid_gid = 1;
			break;
		case OPTION_NO_PRESERVE_OWNER: /* GNU cpio */
			cpio->extract_flags &= ~ARCHIVE_EXTRACT_OWNER;
			break;
		case 'O': /* GNU cpio */
			cpio->filename = cpio->optarg;
			break;
		case 'o': /* POSIX 1997 */
			if (cpio->mode != '\0')
				lafe_errc(1, 0,
				    "Cannot use both -o and -%c", cpio->mode);
			cpio->mode = opt;
			break;
		case 'p': /* POSIX 1997 */
			if (cpio->mode != '\0')
				lafe_errc(1, 0,
				    "Cannot use both -p and -%c", cpio->mode);
			cpio->mode = opt;
			cpio->extract_flags &= ~ARCHIVE_EXTRACT_SECURE_NODOTDOT;
			break;
		case OPTION_PRESERVE_OWNER:
			cpio->extract_flags |= ARCHIVE_EXTRACT_OWNER;
			break;
		case OPTION_QUIET: /* GNU cpio */
			cpio->quiet = 1;
			break;
		case 'R': /* GNU cpio, also --owner */
			errmsg = owner_parse(cpio->optarg, &uid, &gid);
			if (errmsg) {
				lafe_warnc(-1, "%s", errmsg);
				usage();
			}
			if (uid != -1)
				cpio->uid_override = uid;
			if (gid != -1)
				cpio->gid_override = gid;
			break;
		case 'r': /* POSIX 1997 */
			cpio->option_rename = 1;
			break;
		case 't': /* POSIX 1997 */
			cpio->option_list = 1;
			break;
		case 'u': /* POSIX 1997 */
			cpio->extract_flags
			    &= ~ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER;
			break;
		case 'v': /* POSIX 1997 */
			cpio->verbose++;
			break;
		case OPTION_VERSION: /* GNU convention */
			version();
			break;
#if 0
	        /*
		 * cpio_getopt() handles -W specially, so it's not
		 * available here.
		 */
		case 'W': /* Obscure, but useful GNU convention. */
			break;
#endif
		case 'y': /* tar convention */
			cpio->compress = opt;
			break;
		case 'Z': /* tar convention */
			cpio->compress = opt;
			break;
		case 'z': /* tar convention */
			cpio->compress = opt;
			break;
		default:
			usage();
		}
	}

	/*
	 * Sanity-check args, error out on nonsensical combinations.
	 */
	/* -t implies -i if no mode was specified. */
	if (cpio->option_list && cpio->mode == '\0')
		cpio->mode = 'i';
	/* -t requires -i */
	if (cpio->option_list && cpio->mode != 'i')
		lafe_errc(1, 0, "Option -t requires -i");
	/* -n requires -it */
	if (cpio->option_numeric_uid_gid && !cpio->option_list)
		lafe_errc(1, 0, "Option -n requires -it");
	/* Can only specify format when writing */
	if (cpio->format != NULL && cpio->mode != 'o')
		lafe_errc(1, 0, "Option --format requires -o");
	/* -l requires -p */
	if (cpio->option_link && cpio->mode != 'p')
		lafe_errc(1, 0, "Option -l requires -p");
	/* TODO: Flag other nonsensical combinations. */

	switch (cpio->mode) {
	case 'o':
		/* TODO: Implement old binary format in libarchive,
		   use that here. */
		if (cpio->format == NULL)
			cpio->format = "odc"; /* Default format */

		mode_out(cpio);
		break;
	case 'i':
		while (*cpio->argv != NULL) {
			lafe_include(&cpio->matching, *cpio->argv);
			--cpio->argc;
			++cpio->argv;
		}
		if (cpio->option_list)
			mode_list(cpio);
		else
			mode_in(cpio);
		break;
	case 'p':
		if (*cpio->argv == NULL || **cpio->argv == '\0')
			lafe_errc(1, 0,
			    "-p mode requires a target directory");
		mode_pass(cpio, *cpio->argv);
		break;
	default:
		lafe_errc(1, 0,
		    "Must specify at least one of -i, -o, or -p");
	}

	free_cache(cpio->gname_cache);
	free_cache(cpio->uname_cache);
	return (cpio->return_value);
}