Skip to content

Instantly share code, notes, and snippets.

@javiermon
Created July 24, 2014 14:37
Show Gist options
  • Select an option

  • Save javiermon/ae212daf66275f468616 to your computer and use it in GitHub Desktop.

Select an option

Save javiermon/ae212daf66275f468616 to your computer and use it in GitHub Desktop.
untar
/*
* "untar" is an extremely simple tar extractor:
* * A single C source file, so it should be easy to compile
* and run on any system with a C compiler.
* * Extremely portable standard C. The only non-ANSI function
* used is mkdir().
* * Reads basic ustar tar archives.
* * Does not require libarchive or any other special library.
*
* To compile: cc -o untar untar.c
*
* Usage: untar destination <archive>
*
* In particular, this program should be sufficient to extract the
* distribution for libarchive, allowing people to bootstrap
* libarchive on systems that do not already have a tar program.
*
* To unpack libarchive-x.y.z.tar.gz:
* * gunzip libarchive-x.y.z.tar.gz
* * untar libarchive-x.y.z.tar
*
* Written by Tim Kientzle, March 2009.
*
* Released into the public domain.
*/
/* These are all highly standard and portable headers. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* This is for mkdir(); this may need to be changed for some platforms. */
#include <sys/stat.h> /* For mkdir() */
/* Parse an octal number, ignoring leading and trailing nonsense. */
static int
parseoct(const char *p, size_t n)
{
int i = 0;
while (*p < '0' || *p > '7') {
++p;
--n;
}
while (*p >= '0' && *p <= '7' && n > 0) {
i *= 8;
i += *p - '0';
++p;
--n;
}
return (i);
}
/* Returns true if this is 512 zero bytes. */
static int
is_end_of_archive(const char *p)
{
int n;
for (n = 511; n >= 0; --n)
if (p[n] != '\0')
return (0);
return (1);
}
/* Create a directory, including parent directories as necessary. */
static void
create_dir(char *pathname, int mode, const char *destination)
{
char *p;
int r;
char fullpath[512];
/* sanity checks */
if (pathname == NULL)
{
return;
}
if ((strcmp(pathname, ".") || strcmp(pathname, "./")) ||
(strcmp(pathname, "..") || strcmp(pathname, "../")))
{
return;
}
if (snprintf(fullpath, 512, "%s/%s", destination, pathname) >= 512)
{
return;
}
/* Strip trailing '/' */
if (fullpath[strlen(fullpath) - 1] == '/')
fullpath[strlen(fullpath) - 1] = '\0';
/* Try creating the directory. */
r = mkdir(fullpath, mode);
if (r != 0) {
/* On failure, try creating parent directory. */
p = strrchr(fullpath, '/');
if (p != NULL) {
*p = '\0';
create_dir(fullpath, 0755, fullpath);
*p = '/';
r = mkdir(fullpath, mode);
}
}
if (r != 0)
fprintf(stderr, "Could not create directory %s\n", fullpath);
}
/* Create a file, including parent directory as necessary. */
static FILE *
create_file(char *pathname, const char *destination)
{
FILE *f;
char fullpath[512];
if (snprintf(fullpath, 512, "%s/%s", destination, pathname) >= 512)
{
return NULL;
}
f = fopen(fullpath, "w+");
if (f == NULL) {
/* Try creating parent dir and then creating file. */
char *p = strrchr(fullpath, '/');
if (p != NULL) {
*p = '\0';
create_dir(fullpath, 0755, fullpath);
*p = '/';
f = fopen(fullpath, "w+");
}
}
return (f);
}
/* Verify the tar checksum. */
static int
verify_checksum(const char *p)
{
int n, u = 0;
for (n = 0; n < 512; ++n) {
if (n < 148 || n > 155)
/* Standard tar checksum adds unsigned bytes. */
u += ((unsigned char *)p)[n];
else
u += 0x20;
}
return (u == parseoct(p + 148, 8));
}
/* Extract a tar archive. */
static int
untar(FILE *a, const char *path, const char *destination)
{
char buff[512];
FILE *f = NULL;
size_t bytes_read;
int filesize, mode;
printf("Extracting from %s\n", path);
for (;;) {
bytes_read = fread(buff, 1, 512, a);
if (bytes_read < 512) {
fprintf(stderr,
"Short read on %s: expected 512, got %d\n",
path, bytes_read);
return -1;
}
if (is_end_of_archive(buff)) {
printf("End of %s\n", path);
return 0;
}
if (!verify_checksum(buff)) {
fprintf(stderr, "Checksum failure\n");
return -1;
}
filesize = parseoct(buff + 124, 12);
switch (buff[156]) {
case '1':
printf(" Ignoring hardlink %s\n", buff);
break;
case '2':
printf(" Ignoring symlink %s\n", buff);
break;
case '3':
printf(" Ignoring character device %s\n", buff);
break;
case '4':
printf(" Ignoring block device %s\n", buff);
break;
case '5':
printf(" Extracting dir %s\n", buff);
create_dir(buff, parseoct(buff + 100, 8), destination);
filesize = 0;
break;
case '6':
printf(" Ignoring FIFO %s\n", buff);
break;
default:
printf(" Extracting file %s\n", buff);
mode = parseoct(buff + 100, 8);
f = create_file(buff, destination);
break;
}
while (filesize > 0) {
bytes_read = fread(buff, 1, 512, a);
if (bytes_read < 512) {
fprintf(stderr,
"Short read on %s: Expected 512, got %d\n",
path, bytes_read);
return -1;
}
if (filesize < 512)
bytes_read = filesize;
if (f != NULL) {
if (fwrite(buff, 1, bytes_read, f)
!= bytes_read)
{
fprintf(stderr, "Failed write\n");
fclose(f);
f = NULL;
}
}
filesize -= bytes_read;
}
if (f != NULL) {
if (mode != 0 && (fchmod(fileno(f), mode) < 0))
{
fprintf(stderr,
"Unable to chmod %u file\n", mode);
}
fclose(f);
f = NULL;
}
}
return 0;
}
int
main(int argc, char **argv)
{
FILE *a;
char *destination;
struct stat sb;
++argv; /* Skip program name */
destination = *argv;
if (stat(destination, &sb) != 0 || !S_ISDIR(sb.st_mode))
{
fprintf(stderr, "Unable to open %s\n", destination);
return -1;
}
++argv; /* Skip destination folder name */
for ( ;*argv != NULL; ++argv) {
a = fopen(*argv, "r");
if (a == NULL)
fprintf(stderr, "Unable to open %s\n", *argv);
else {
untar(a, *argv, destination);
fclose(a);
}
}
return (0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment