static const char *long_help_msg =
"First option must be a mode specifier:\n"
" -i Input -o Output -p Pass\n"
"Common Options:\n"
" -v Verbose filenames -V one dot per file\n"
"Create: %p -o [options] < [list of files] > [archive]\n"
" -J,-y,-z,--lzma Compress archive with xz/bzip2/gzip/lzma\n"
" --format {pwb|bin|odc|newc|ustar} Select archive format\n"
"List: %p -it < [archive]\n"
"Extract: %p -i [options] < [archive]\n";
/*
* Note that the word 'bsdcpio' will always appear in the first line
* of output.
*
* In particular, /bin/sh scripts that need to test for the presence
* of bsdcpio can use the following template:
*
* if (cpio --help 2>&1 | grep bsdcpio >/dev/null 2>&1 ) then \
* echo bsdcpio; else echo not bsdcpio; fi
*/
static void
long_help(void)
{
const char *prog;
const char *p;
if (cpio->option_append)
lafe_errc(1, 0, "Append mode not yet supported.");
cpio->archive_read_disk = archive_read_disk_new();
if (cpio->archive_read_disk == NULL)
lafe_errc(1, 0, "Failed to allocate archive object");
if (cpio->option_follow_links)
archive_read_disk_set_symlink_logical(cpio->archive_read_disk);
else
archive_read_disk_set_symlink_physical(cpio->archive_read_disk);
archive_read_disk_set_standard_lookup(cpio->archive_read_disk);
cpio->archive = archive_write_new();
if (cpio->archive == NULL)
lafe_errc(1, 0, "Failed to allocate archive object");
switch (cpio->compress) {
case OPTION_GRZIP:
r = archive_write_add_filter_grzip(cpio->archive);
break;
case 'J':
r = archive_write_add_filter_xz(cpio->archive);
break;
case OPTION_LRZIP:
r = archive_write_add_filter_lrzip(cpio->archive);
break;
case OPTION_LZ4:
r = archive_write_add_filter_lz4(cpio->archive);
break;
case OPTION_LZMA:
r = archive_write_add_filter_lzma(cpio->archive);
break;
case OPTION_LZOP:
r = archive_write_add_filter_lzop(cpio->archive);
break;
case OPTION_ZSTD:
r = archive_write_add_filter_zstd(cpio->archive);
break;
case 'j': case 'y':
r = archive_write_add_filter_bzip2(cpio->archive);
break;
case 'z':
r = archive_write_add_filter_gzip(cpio->archive);
break;
case 'Z':
r = archive_write_add_filter_compress(cpio->archive);
break;
default:
r = archive_write_add_filter_none(cpio->archive);
break;
}
if (r < ARCHIVE_WARN)
lafe_errc(1, 0, "Requested compression not available");
switch (cpio->add_filter) {
case 0:
r = ARCHIVE_OK;
break;
case OPTION_B64ENCODE:
r = archive_write_add_filter_b64encode(cpio->archive);
break;
case OPTION_UUENCODE:
r = archive_write_add_filter_uuencode(cpio->archive);
break;
}
if (r < ARCHIVE_WARN)
lafe_errc(1, 0, "Requested filter not available");
r = archive_write_set_format_by_name(cpio->archive, cpio->format);
if (r != ARCHIVE_OK)
lafe_errc(1, 0, "%s", archive_error_string(cpio->archive));
archive_write_set_bytes_per_block(cpio->archive, cpio->bytes_per_block);
cpio->linkresolver = archive_entry_linkresolver_new();
archive_entry_linkresolver_set_strategy(cpio->linkresolver,
archive_format(cpio->archive));
if (cpio->passphrase != NULL)
r = archive_write_set_passphrase(cpio->archive,
cpio->passphrase);
else
r = archive_write_set_passphrase_callback(cpio->archive, cpio,
&passphrase_callback);
if (r != ARCHIVE_OK)
lafe_errc(1, 0, "%s", archive_error_string(cpio->archive));
/*
* The main loop: Copy each file into the output archive.
*/
r = archive_write_open_filename(cpio->archive, cpio->filename);
if (r != ARCHIVE_OK)
lafe_errc(1, 0, "%s", archive_error_string(cpio->archive));
lr = lafe_line_reader("-", cpio->option_null);
while ((p = lafe_line_reader_next(lr)) != NULL)
file_to_archive(cpio, p);
lafe_line_reader_free(lr);
/*
* The hardlink detection may have queued up a couple of entries
* that can now be flushed.
*/
entry = NULL;
archive_entry_linkify(cpio->linkresolver, &entry, &spare);
while (entry != NULL) {
entry_to_archive(cpio, entry);
archive_entry_free(entry);
entry = NULL;
archive_entry_linkify(cpio->linkresolver, &entry, &spare);
}
r = archive_write_close(cpio->archive);
if (cpio->dot)
fprintf(stderr, "\n");
if (r != ARCHIVE_OK)
lafe_errc(1, 0, "%s", archive_error_string(cpio->archive));
/* 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;
}
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;
}
/* 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
p += 1; /* Remove "/". */
}
} while (rp != p);
return (p);
}
/*
* 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;
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) {
archive_entry_free(entry);
cpio->return_value = 1;
return (r);
}
if (cpio->uid_override >= 0)
archive_entry_set_uid(entry, cpio->uid_override);
if (cpio->gname_override != NULL)
archive_entry_set_uname(entry, cpio->uname_override);
if (cpio->gid_override >= 0)
archive_entry_set_gid(entry, cpio->gid_override);
if (cpio->gname_override != NULL)
archive_entry_set_gname(entry, cpio->gname_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 = cpio->destdir_len + 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);
strcat(cpio->pass_destpath, remove_leading_slash(srcpath));
destpath = cpio->pass_destpath;
}
if (cpio->option_rename)
destpath = cpio_rename(destpath);
if (destpath == NULL) {
archive_entry_free(entry);
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);
}
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);
if (cpio->dot)
fprintf(stderr, ".");
/*
* 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 && archive_entry_size(entry) > 0 && fd >= 0) {
bytes_read = read(fd, cpio->buff, (unsigned)cpio->buff_size);
while (bytes_read > 0) {
ssize_t bytes_write;
bytes_write = archive_write_data(cpio->archive,
cpio->buff, bytes_read);
if (bytes_write < 0)
lafe_errc(1, archive_errno(cpio->archive),
"%s", archive_error_string(cpio->archive));
if (bytes_write < bytes_read) {
lafe_warnc(0,
"Truncated write; file may have "
"grown while being archived.");
}
bytes_read = read(fd, cpio->buff,
(unsigned)cpio->buff_size);
}
}
fd = restore_time(cpio, entry, srcpath, fd);
cleanup:
if (cpio->verbose)
fprintf(stderr,"\n");
if (fd >= 0)
close(fd);
return (0);
}
static int
restore_time(struct cpio *cpio, struct archive_entry *entry,
const char *name, int fd)
{
#ifndef HAVE_UTIMES
static int warned = 0;
a = archive_read_new();
if (a == NULL)
lafe_errc(1, 0, "Couldn't allocate archive object");
archive_read_support_filter_all(a);
archive_read_support_format_all(a);
if (cpio->option_pwb)
archive_read_set_options(a, "pwb");
if (cpio->passphrase != NULL)
r = archive_read_add_passphrase(a, cpio->passphrase);
else
r = archive_read_set_passphrase_callback(a, cpio,
&passphrase_callback);
if (r != ARCHIVE_OK)
lafe_errc(1, 0, "%s", archive_error_string(a));
if (archive_read_open_filename(a, cpio->filename,
cpio->bytes_per_block))
lafe_errc(1, archive_errno(a),
"%s", archive_error_string(a));
for (;;) {
r = archive_read_next_header(a, &entry);
if (r == ARCHIVE_EOF)
break;
if (r != ARCHIVE_OK) {
lafe_errc(1, archive_errno(a),
"%s", archive_error_string(a));
}
if (archive_match_path_excluded(cpio->matching, entry))
continue;
if (cpio->verbose)
list_item_verbose(cpio, entry);
else
fprintf(stdout, "%s\n", archive_entry_pathname(entry));
}
r = archive_read_close(a);
if (r != ARCHIVE_OK)
lafe_errc(1, 0, "%s", archive_error_string(a));
if (!cpio->quiet) {
int64_t blocks = (archive_filter_bytes(a, 0) + 511)
/ 512;
fprintf(stderr, "%lu %s\n", (unsigned long)blocks,
blocks == 1 ? "block" : "blocks");
}
archive_read_free(a);
exit(0);
}
/*
* Display information about the current file.
*
* The format here roughly duplicates the output of 'ls -l'.
* This is based on SUSv2, where 'tar tv' is documented as
* listing additional information in an "unspecified format,"
* and 'pax -l' is documented as using the same format as 'ls -l'.
*/
static void
list_item_verbose(struct cpio *cpio, struct archive_entry *entry)
{
char size[32];
char date[32];
char uids[22], gids[22];
const char *uname, *gname;
FILE *out = stdout;
const char *fmt;
time_t mtime;
static time_t now;
struct tm *ltime;
#if defined(HAVE_LOCALTIME_R) || defined(HAVE_LOCALTIME_S)
struct tm tmbuf;
#endif
if (!now)
time(&now);
if (cpio->option_numeric_uid_gid) {
/* Format numeric uid/gid for display. */
strcpy(uids, cpio_i64toa(archive_entry_uid(entry)));
uname = uids;
strcpy(gids, cpio_i64toa(archive_entry_gid(entry)));
gname = gids;
} else {
/* Use uname if it's present, else lookup name from uid. */
uname = archive_entry_uname(entry);
if (uname == NULL)
uname = lookup_uname(cpio, (uid_t)archive_entry_uid(entry));
/* Use gname if it's present, else lookup name from gid. */
gname = archive_entry_gname(entry);
if (gname == NULL)
gname = lookup_gname(cpio, (uid_t)archive_entry_gid(entry));
}
/* Print device number or file size. */
if (archive_entry_filetype(entry) == AE_IFCHR
|| archive_entry_filetype(entry) == AE_IFBLK) {
snprintf(size, sizeof(size), "%lu,%lu",
(unsigned long)archive_entry_rdevmajor(entry),
(unsigned long)archive_entry_rdevminor(entry));
} else {
strcpy(size, cpio_i64toa(archive_entry_size(entry)));
}
/* Format the time using 'ls -l' conventions. */
mtime = archive_entry_mtime(entry);
#if defined(_WIN32) && !defined(__CYGWIN__)
/* Windows' strftime function does not support %e format. */
if (mtime - now > 365*86400/2
|| mtime - now < -365*86400/2)
fmt = cpio->day_first ? "%d %b %Y" : "%b %d %Y";
else
fmt = cpio->day_first ? "%d %b %H:%M" : "%b %d %H:%M";
#else
if (mtime - now > 365*86400/2
|| mtime - now < -365*86400/2)
fmt = cpio->day_first ? "%e %b %Y" : "%b %e %Y";
else
fmt = cpio->day_first ? "%e %b %H:%M" : "%b %e %H:%M";
#endif
#if defined(HAVE_LOCALTIME_S)
ltime = localtime_s(&tmbuf, &mtime) ? NULL : &tmbuf;
#elif defined(HAVE_LOCALTIME_R)
ltime = localtime_r(&mtime, &tmbuf);
#else
ltime = localtime(&mtime);
#endif
if (ltime != NULL)
strftime(date, sizeof(date), fmt, ltime);
else
strcpy(date, "invalid mtime");
/* Extra information for links. */
if (archive_entry_hardlink(entry)) /* Hard link */
fprintf(out, " link to %s", archive_entry_hardlink(entry));
else if (archive_entry_symlink(entry)) /* Symbolic link */
fprintf(out, " -> %s", archive_entry_symlink(entry));
fprintf(out, "\n");
}
/*
* Prompt for a new name for this entry. Returns a pointer to the
* new name or NULL if the entry should not be copied. This
* implements the semantics defined in POSIX.1-1996, which specifies
* that an input of '.' means the name should be unchanged. GNU cpio
* treats '.' as a literal new name.
*/
static const char *
cpio_rename(const char *name)
{
static char buff[1024];
FILE *t;
char *p, *ret;
#if defined(_WIN32) && !defined(__CYGWIN__)
FILE *to;
t = fopen("CONIN$", "r");
if (t == NULL)
return (name);
to = fopen("CONOUT$", "w");
if (to == NULL) {
fclose(t);
return (name);
}
fprintf(to, "%s (Enter/./(new name))? ", name);
fclose(to);
#else
t = fopen("/dev/tty", "r+");
if (t == NULL)
return (name);
fprintf(t, "%s (Enter/./(new name))? ", name);
fflush(t);
#endif
p = fgets(buff, sizeof(buff), t);
fclose(t);
if (p == NULL)
/* End-of-file is a blank line. */
return (NULL);
while (*p == ' ' || *p == '\t')
++p;
if (*p == '\n' || *p == '\0')
/* Empty line. */
return (NULL);
if (*p == '.' && p[1] == '\n')
/* Single period preserves original name. */
return (name);
ret = p;
/* Trim the final newline. */
while (*p != '\0' && *p != '\n')
++p;
/* Overwrite the final \n with a null character. */
*p = '\0';
return (ret);
}
/*
* It would be nice to just use printf() for formatting large numbers,
* but the compatibility problems are a big headache. Hence the
* following simple utility function.
*/
const char *
cpio_i64toa(int64_t n0)
{
/* 2^64 =~ 1.8 * 10^19, so 20 decimal digits suffice.
* We also need 1 byte for '-' and 1 for '\0'.
*/
static char buff[22];
int64_t n = n0 < 0 ? -n0 : n0;
char *p = buff + sizeof(buff);
*--p = '\0';
do {
*--p = '0' + (int)(n % 10);
n /= 10;
} while (n > 0);
if (n0 < 0)
*--p = '-';
return p;
}