Blame examples/untar.c

Packit Service 1d0348
/*
Packit Service 1d0348
 * This file is in the public domain.
Packit Service 1d0348
 * Use it as you wish.
Packit Service 1d0348
 */
Packit Service 1d0348
Packit Service 1d0348
/*
Packit Service 1d0348
 * This is a compact tar extraction program using libarchive whose
Packit Service 1d0348
 * primary goal is small executable size.  Statically linked, it can
Packit Service 1d0348
 * be very small, depending in large part on how cleanly factored your
Packit Service 1d0348
 * system libraries are.  Note that this uses the standard libarchive,
Packit Service 1d0348
 * without any special recompilation.  The only functional concession
Packit Service 1d0348
 * is that this program uses the uid/gid from the archive instead of
Packit Service 1d0348
 * doing uname/gname lookups.  (Add a call to
Packit Service 1d0348
 * archive_write_disk_set_standard_lookup() to enable uname/gname
Packit Service 1d0348
 * lookups, but be aware that this can add 500k or more to a static
Packit Service 1d0348
 * executable, depending on the system libraries, since user/group
Packit Service 1d0348
 * lookups frequently pull in password, YP/LDAP, networking, and DNS
Packit Service 1d0348
 * resolver libraries.)
Packit Service 1d0348
 *
Packit Service 1d0348
 * To build:
Packit Service 1d0348
 * $ gcc -static -Wall -o untar untar.c -larchive
Packit Service 1d0348
 * $ strip untar
Packit Service 1d0348
 *
Packit Service 1d0348
 * NOTE: On some systems, you may need to add additional flags
Packit Service 1d0348
 * to ensure that untar.c is compiled the same way as libarchive
Packit Service 1d0348
 * was compiled.  In particular, Linux users will probably
Packit Service 1d0348
 * have to add -D_FILE_OFFSET_BITS=64 to the command line above.
Packit Service 1d0348
 *
Packit Service 1d0348
 * For fun, statically compile the following simple hello.c program
Packit Service 1d0348
 * using the same flags as for untar and compare the size:
Packit Service 1d0348
 *
Packit Service 1d0348
 * #include <stdio.h>
Packit Service 1d0348
 * int main(int argc, char **argv) {
Packit Service 1d0348
 *    printf("hello, world\n");
Packit Service 1d0348
 *    return(0);
Packit Service 1d0348
 * }
Packit Service 1d0348
 *
Packit Service 1d0348
 * You may be even more surprised by the compiled size of true.c listed here:
Packit Service 1d0348
 *
Packit Service 1d0348
 * int main(int argc, char **argv) {
Packit Service 1d0348
 *    return (0);
Packit Service 1d0348
 * }
Packit Service 1d0348
 *
Packit Service 1d0348
 * On a slightly customized FreeBSD 5 system that I used around
Packit Service 1d0348
 * 2005, hello above compiled to 89k compared to untar of 69k.  So at
Packit Service 1d0348
 * that time, libarchive's tar reader and extract-to-disk routines
Packit Service 1d0348
 * compiled to less code than printf().
Packit Service 1d0348
 *
Packit Service 1d0348
 * On my FreeBSD development system today (August, 2009):
Packit Service 1d0348
 *  hello: 195024 bytes
Packit Service 1d0348
 *  true: 194912 bytes
Packit Service 1d0348
 *  untar: 259924 bytes
Packit Service 1d0348
 */
Packit Service 1d0348
Packit Service 1d0348
#include <sys/types.h>
Packit Service 1d0348
Packit Service 1d0348
#include <sys/stat.h>
Packit Service 1d0348
Packit Service 1d0348
#include <archive.h>
Packit Service 1d0348
#include <archive_entry.h>
Packit Service 1d0348
#include <fcntl.h>
Packit Service 1d0348
#include <stdio.h>
Packit Service 1d0348
#include <stdlib.h>
Packit Service 1d0348
#include <string.h>
Packit Service 1d0348
#include <unistd.h>
Packit Service 1d0348
Packit Service 1d0348
static void	errmsg(const char *);
Packit Service 1d0348
static void	extract(const char *filename, int do_extract, int flags);
Packit Service 1d0348
static void	fail(const char *, const char *, int);
Packit Service 1d0348
static int	copy_data(struct archive *, struct archive *);
Packit Service 1d0348
static void	msg(const char *);
Packit Service 1d0348
static void	usage(void);
Packit Service 1d0348
static void	warn(const char *, const char *);
Packit Service 1d0348
Packit Service 1d0348
static int verbose = 0;
Packit Service 1d0348
Packit Service 1d0348
int
Packit Service 1d0348
main(int argc, const char **argv)
Packit Service 1d0348
{
Packit Service 1d0348
	const char *filename = NULL;
Packit Service 1d0348
	int compress, flags, mode, opt;
Packit Service 1d0348
Packit Service 1d0348
	(void)argc;
Packit Service 1d0348
	mode = 'x';
Packit Service 1d0348
	verbose = 0;
Packit Service 1d0348
	compress = '\0';
Packit Service 1d0348
	flags = ARCHIVE_EXTRACT_TIME;
Packit Service 1d0348
Packit Service 1d0348
	/* Among other sins, getopt(3) pulls in printf(3). */
Packit Service 1d0348
	while (*++argv != NULL && **argv == '-') {
Packit Service 1d0348
		const char *p = *argv + 1;
Packit Service 1d0348
Packit Service 1d0348
		while ((opt = *p++) != '\0') {
Packit Service 1d0348
			switch (opt) {
Packit Service 1d0348
			case 'f':
Packit Service 1d0348
				if (*p != '\0')
Packit Service 1d0348
					filename = p;
Packit Service 1d0348
				else
Packit Service 1d0348
					filename = *++argv;
Packit Service 1d0348
				p += strlen(p);
Packit Service 1d0348
				break;
Packit Service 1d0348
			case 'p':
Packit Service 1d0348
				flags |= ARCHIVE_EXTRACT_PERM;
Packit Service 1d0348
				flags |= ARCHIVE_EXTRACT_ACL;
Packit Service 1d0348
				flags |= ARCHIVE_EXTRACT_FFLAGS;
Packit Service 1d0348
				break;
Packit Service 1d0348
			case 't':
Packit Service 1d0348
				mode = opt;
Packit Service 1d0348
				break;
Packit Service 1d0348
			case 'v':
Packit Service 1d0348
				verbose++;
Packit Service 1d0348
				break;
Packit Service 1d0348
			case 'x':
Packit Service 1d0348
				mode = opt;
Packit Service 1d0348
				break;
Packit Service 1d0348
			default:
Packit Service 1d0348
				usage();
Packit Service 1d0348
			}
Packit Service 1d0348
		}
Packit Service 1d0348
	}
Packit Service 1d0348
Packit Service 1d0348
	switch (mode) {
Packit Service 1d0348
	case 't':
Packit Service 1d0348
		extract(filename, 0, flags);
Packit Service 1d0348
		break;
Packit Service 1d0348
	case 'x':
Packit Service 1d0348
		extract(filename, 1, flags);
Packit Service 1d0348
		break;
Packit Service 1d0348
	}
Packit Service 1d0348
Packit Service 1d0348
	return (0);
Packit Service 1d0348
}
Packit Service 1d0348
Packit Service 1d0348
Packit Service 1d0348
static void
Packit Service 1d0348
extract(const char *filename, int do_extract, int flags)
Packit Service 1d0348
{
Packit Service 1d0348
	struct archive *a;
Packit Service 1d0348
	struct archive *ext;
Packit Service 1d0348
	struct archive_entry *entry;
Packit Service 1d0348
	int r;
Packit Service 1d0348
Packit Service 1d0348
	a = archive_read_new();
Packit Service 1d0348
	ext = archive_write_disk_new();
Packit Service 1d0348
	archive_write_disk_set_options(ext, flags);
Packit Service 1d0348
	/*
Packit Service 1d0348
	 * Note: archive_write_disk_set_standard_lookup() is useful
Packit Service 1d0348
	 * here, but it requires library routines that can add 500k or
Packit Service 1d0348
	 * more to a static executable.
Packit Service 1d0348
	 */
Packit Service 1d0348
	archive_read_support_format_tar(a);
Packit Service 1d0348
	/*
Packit Service 1d0348
	 * On my system, enabling other archive formats adds 20k-30k
Packit Service 1d0348
	 * each.  Enabling gzip decompression adds about 20k.
Packit Service 1d0348
	 * Enabling bzip2 is more expensive because the libbz2 library
Packit Service 1d0348
	 * isn't very well factored.
Packit Service 1d0348
	 */
Packit Service 1d0348
	if (filename != NULL && strcmp(filename, "-") == 0)
Packit Service 1d0348
		filename = NULL;
Packit Service 1d0348
	if ((r = archive_read_open_filename(a, filename, 10240)))
Packit Service 1d0348
		fail("archive_read_open_filename()",
Packit Service 1d0348
		    archive_error_string(a), r);
Packit Service 1d0348
	for (;;) {
Packit Service 1d0348
		r = archive_read_next_header(a, &entry);
Packit Service 1d0348
		if (r == ARCHIVE_EOF)
Packit Service 1d0348
			break;
Packit Service 1d0348
		if (r != ARCHIVE_OK)
Packit Service 1d0348
			fail("archive_read_next_header()",
Packit Service 1d0348
			    archive_error_string(a), 1);
Packit Service 1d0348
		if (verbose && do_extract)
Packit Service 1d0348
			msg("x ");
Packit Service 1d0348
		if (verbose || !do_extract)
Packit Service 1d0348
			msg(archive_entry_pathname(entry));
Packit Service 1d0348
		if (do_extract) {
Packit Service 1d0348
			r = archive_write_header(ext, entry);
Packit Service 1d0348
			if (r != ARCHIVE_OK)
Packit Service 1d0348
				warn("archive_write_header()",
Packit Service 1d0348
				    archive_error_string(ext));
Packit Service 1d0348
			else {
Packit Service 1d0348
				copy_data(a, ext);
Packit Service 1d0348
				r = archive_write_finish_entry(ext);
Packit Service 1d0348
				if (r != ARCHIVE_OK)
Packit Service 1d0348
					fail("archive_write_finish_entry()",
Packit Service 1d0348
					    archive_error_string(ext), 1);
Packit Service 1d0348
			}
Packit Service 1d0348
Packit Service 1d0348
		}
Packit Service 1d0348
		if (verbose || !do_extract)
Packit Service 1d0348
			msg("\n");
Packit Service 1d0348
	}
Packit Service 1d0348
	archive_read_close(a);
Packit Service 1d0348
	archive_read_free(a);
Packit Service 1d0348
	
Packit Service 1d0348
	archive_write_close(ext);
Packit Service 1d0348
  	archive_write_free(ext);
Packit Service 1d0348
	exit(0);
Packit Service 1d0348
}
Packit Service 1d0348
Packit Service 1d0348
static int
Packit Service 1d0348
copy_data(struct archive *ar, struct archive *aw)
Packit Service 1d0348
{
Packit Service 1d0348
	int r;
Packit Service 1d0348
	const void *buff;
Packit Service 1d0348
	size_t size;
Packit Service 1d0348
#if ARCHIVE_VERSION_NUMBER >= 3000000
Packit Service 1d0348
	int64_t offset;
Packit Service 1d0348
#else
Packit Service 1d0348
	off_t offset;
Packit Service 1d0348
#endif
Packit Service 1d0348
Packit Service 1d0348
	for (;;) {
Packit Service 1d0348
		r = archive_read_data_block(ar, &buff, &size, &offset);
Packit Service 1d0348
		if (r == ARCHIVE_EOF)
Packit Service 1d0348
			return (ARCHIVE_OK);
Packit Service 1d0348
		if (r != ARCHIVE_OK)
Packit Service 1d0348
			return (r);
Packit Service 1d0348
		r = archive_write_data_block(aw, buff, size, offset);
Packit Service 1d0348
		if (r != ARCHIVE_OK) {
Packit Service 1d0348
			warn("archive_write_data_block()",
Packit Service 1d0348
			    archive_error_string(aw));
Packit Service 1d0348
			return (r);
Packit Service 1d0348
		}
Packit Service 1d0348
	}
Packit Service 1d0348
}
Packit Service 1d0348
Packit Service 1d0348
/*
Packit Service 1d0348
 * These reporting functions use low-level I/O; on some systems, this
Packit Service 1d0348
 * is a significant code reduction.  Of course, on many server and
Packit Service 1d0348
 * desktop operating systems, malloc() and even crt rely on printf(),
Packit Service 1d0348
 * which in turn pulls in most of the rest of stdio, so this is not an
Packit Service 1d0348
 * optimization at all there.  (If you're going to pay 100k or more
Packit Service 1d0348
 * for printf() anyway, you may as well use it!)
Packit Service 1d0348
 */
Packit Service 1d0348
static void
Packit Service 1d0348
msg(const char *m)
Packit Service 1d0348
{
Packit Service 1d0348
	write(1, m, strlen(m));
Packit Service 1d0348
}
Packit Service 1d0348
Packit Service 1d0348
static void
Packit Service 1d0348
errmsg(const char *m)
Packit Service 1d0348
{
Packit Service 1d0348
	write(2, m, strlen(m));
Packit Service 1d0348
}
Packit Service 1d0348
Packit Service 1d0348
static void
Packit Service 1d0348
warn(const char *f, const char *m)
Packit Service 1d0348
{
Packit Service 1d0348
	errmsg(f);
Packit Service 1d0348
	errmsg(" failed: ");
Packit Service 1d0348
	errmsg(m);
Packit Service 1d0348
	errmsg("\n");
Packit Service 1d0348
}
Packit Service 1d0348
Packit Service 1d0348
static void
Packit Service 1d0348
fail(const char *f, const char *m, int r)
Packit Service 1d0348
{
Packit Service 1d0348
	warn(f, m);
Packit Service 1d0348
	exit(r);
Packit Service 1d0348
}
Packit Service 1d0348
Packit Service 1d0348
static void
Packit Service 1d0348
usage(void)
Packit Service 1d0348
{
Packit Service 1d0348
	const char *m = "Usage: untar [-tvx] [-f file] [file]\n";
Packit Service 1d0348
	errmsg(m);
Packit Service 1d0348
	exit(1);
Packit Service 1d0348
}