/* * This file is in the public domain. * Use it as you wish. */ /* * This is a compact tar extraction program whose primary goal is * small size. Statically linked, it can be under 64k, depending on * how cleanly factored your system libraries are. Note that this * uses the standard libarchive, without any special recompilation. * The only functional concession is that this program uses the * uid/gid from the archive instead of doing uname/gname lookups. * (Call archive_write_disk_set_standard_lookup() to enable * uname/gname lookups, but be aware that this can add 500k or more to * a static executable, depending on the system libraries.) * * To build: * gcc -static -Wall -o untar untar.c -larchive * strip untar * * For fun, statically compile the following simple hello.c program * and compare the size. (On my system, the result is 89k, untar is * 69k.) * * #include * int main(int argc, char **argv) { * printf("hello, world\n"); * return(0); * } */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include static void errmsg(const char *); static void extract(const char *filename, int do_extract, int flags); static int copy_data(struct archive *, struct archive *); static void msg(const char *); static void usage(void); static int verbose = 0; int main(int argc, const char **argv) { const char *filename = NULL; int compress, flags, mode, opt; (void)argc; mode = 'x'; verbose = 0; compress = '\0'; flags = ARCHIVE_EXTRACT_TIME; /* Among other sins, getopt(3) pulls in printf(3). */ while (*++argv != NULL && **argv == '-') { const char *p = *argv + 1; while ((opt = *p++) != '\0') { switch (opt) { case 'f': if (*p != '\0') filename = p; else filename = *++argv; p += strlen(p); break; case 'p': flags |= ARCHIVE_EXTRACT_PERM; flags |= ARCHIVE_EXTRACT_ACL; flags |= ARCHIVE_EXTRACT_FFLAGS; break; case 't': mode = opt; break; case 'v': verbose++; break; case 'x': mode = opt; break; default: usage(); } } } switch (mode) { case 't': extract(filename, 0, flags); break; case 'x': extract(filename, 1, flags); break; } return (0); } static void extract(const char *filename, int do_extract, int flags) { struct archive *a; struct archive *ext; struct archive_entry *entry; int r; a = archive_read_new(); ext = archive_write_disk_new(); archive_write_disk_set_options(ext, flags); /* * Note: archive_write_disk_set_standard_lookup() is useful * here, but it requires library routines that can add 500k or * more to a static executable. */ archive_read_support_format_tar(a); /* * On my system, enabling other archive formats adds 20k-30k * each. Enabling gzip decompression adds about 20k. * Enabling bzip2 is more expensive because the libbz2 library * isn't very well factored. */ if (filename != NULL && strcmp(filename, "-") == 0) filename = NULL; if ((r = archive_read_open_file(a, filename, 10240))) { errmsg(archive_error_string(a)); errmsg("\n"); exit(r); } for (;;) { r = archive_read_next_header(a, &entry); if (r == ARCHIVE_EOF) break; if (r != ARCHIVE_OK) { errmsg(archive_error_string(a)); errmsg("\n"); exit(1); } if (verbose && do_extract) msg("x "); if (verbose || !do_extract) msg(archive_entry_pathname(entry)); if (do_extract) { r = archive_write_header(ext, entry); if (r != ARCHIVE_OK) errmsg(archive_error_string(a)); else copy_data(a, ext); } if (verbose || !do_extract) msg("\n"); } archive_read_close(a); archive_read_finish(a); exit(0); } static int copy_data(struct archive *ar, struct archive *aw) { int r; const void *buff; size_t size; off_t offset; for (;;) { r = archive_read_data_block(ar, &buff, &size, &offset); if (r == ARCHIVE_EOF) { errmsg(archive_error_string(ar)); return (ARCHIVE_OK); } if (r != ARCHIVE_OK) return (r); r = archive_write_data_block(aw, buff, size, offset); if (r != ARCHIVE_OK) { errmsg(archive_error_string(ar)); return (r); } } } static void msg(const char *m) { write(1, m, strlen(m)); } static void errmsg(const char *m) { write(2, m, strlen(m)); } static void usage(void) { const char *m = "Usage: untar [-tvx] [-f file] [file]\n"; errmsg(m); exit(1); }