1/*
2 * "untar" is an extremely simple tar extractor:
3 *  * A single C source file, so it should be easy to compile
4 *    and run on any system with a C compiler.
5 *  * Extremely portable standard C.  The only non-ANSI function
6 *    used is mkdir().
7 *  * Reads basic ustar tar archives.
8 *  * Does not require libarchive or any other special library.
9 *
10 * To compile: cc -o untar untar.c
11 *
12 * Usage:  untar <archive>
13 *
14 * In particular, this program should be sufficient to extract the
15 * distribution for libarchive, allowing people to bootstrap
16 * libarchive on systems that do not already have a tar program.
17 *
18 * To unpack libarchive-x.y.z.tar.gz:
19 *    * gunzip libarchive-x.y.z.tar.gz
20 *    * untar libarchive-x.y.z.tar
21 *
22 * Written by Tim Kientzle, March 2009.
23 *
24 * Released into the public domain.
25 */
26
27/* These are all highly standard and portable headers. */
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31
32/* This is for mkdir(); this may need to be changed for some platforms. */
33#include <sys/stat.h>  /* For mkdir() */
34
35/* Parse an octal number, ignoring leading and trailing nonsense. */
36static int
37parseoct(const char *p, size_t n)
38{
39	int i = 0;
40
41	while (*p < '0' || *p > '7') {
42		++p;
43		--n;
44	}
45	while (*p >= '0' && *p <= '7' && n > 0) {
46		i *= 8;
47		i += *p - '0';
48		++p;
49		--n;
50	}
51	return (i);
52}
53
54/* Returns true if this is 512 zero bytes. */
55static int
56is_end_of_archive(const char *p)
57{
58	int n;
59	for (n = 511; n >= 0; --n)
60		if (p[n] != '\0')
61			return (0);
62	return (1);
63}
64
65/* Create a directory, including parent directories as necessary. */
66static void
67create_dir(char *pathname, int mode)
68{
69	char *p;
70	int r;
71
72	/* Strip trailing '/' */
73	if (pathname[strlen(pathname) - 1] == '/')
74		pathname[strlen(pathname) - 1] = '\0';
75
76	/* Try creating the directory. */
77	r = mkdir(pathname, mode);
78
79	if (r != 0) {
80		/* On failure, try creating parent directory. */
81		p = strrchr(pathname, '/');
82		if (p != NULL) {
83			*p = '\0';
84			create_dir(pathname, 0755);
85			*p = '/';
86			r = mkdir(pathname, mode);
87		}
88	}
89	if (r != 0)
90		fprintf(stderr, "Could not create directory %s\n", pathname);
91}
92
93/* Create a file, including parent directory as necessary. */
94static FILE *
95create_file(char *pathname, int mode)
96{
97	FILE *f;
98	f = fopen(pathname, "w+");
99	if (f == NULL) {
100		/* Try creating parent dir and then creating file. */
101		char *p = strrchr(pathname, '/');
102		if (p != NULL) {
103			*p = '\0';
104			create_dir(pathname, 0755);
105			*p = '/';
106			f = fopen(pathname, "w+");
107		}
108	}
109	return (f);
110}
111
112/* Verify the tar checksum. */
113static int
114verify_checksum(const char *p)
115{
116	int n, u = 0;
117	for (n = 0; n < 512; ++n) {
118		if (n < 148 || n > 155)
119			/* Standard tar checksum adds unsigned bytes. */
120			u += ((unsigned char *)p)[n];
121		else
122			u += 0x20;
123
124	}
125	return (u == parseoct(p + 148, 8));
126}
127
128/* Extract a tar archive. */
129static void
130untar(FILE *a, const char *path)
131{
132	char buff[512];
133	FILE *f = NULL;
134	size_t bytes_read;
135	int filesize;
136
137	printf("Extracting from %s\n", path);
138	for (;;) {
139		bytes_read = fread(buff, 1, 512, a);
140		if (bytes_read < 512) {
141			fprintf(stderr,
142			    "Short read on %s: expected 512, got %d\n",
143			    path, bytes_read);
144			return;
145		}
146		if (is_end_of_archive(buff)) {
147			printf("End of %s\n", path);
148			return;
149		}
150		if (!verify_checksum(buff)) {
151			fprintf(stderr, "Checksum failure\n");
152			return;
153		}
154		filesize = parseoct(buff + 124, 12);
155		switch (buff[156]) {
156		case '1':
157			printf(" Ignoring hardlink %s\n", buff);
158			break;
159		case '2':
160			printf(" Ignoring symlink %s\n", buff);
161			break;
162		case '3':
163			printf(" Ignoring character device %s\n", buff);
164				break;
165		case '4':
166			printf(" Ignoring block device %s\n", buff);
167			break;
168		case '5':
169			printf(" Extracting dir %s\n", buff);
170			create_dir(buff, parseoct(buff + 100, 8));
171			filesize = 0;
172			break;
173		case '6':
174			printf(" Ignoring FIFO %s\n", buff);
175			break;
176		default:
177			printf(" Extracting file %s\n", buff);
178			f = create_file(buff, parseoct(buff + 100, 8));
179			break;
180		}
181		while (filesize > 0) {
182			bytes_read = fread(buff, 1, 512, a);
183			if (bytes_read < 512) {
184				fprintf(stderr,
185				    "Short read on %s: Expected 512, got %d\n",
186				    path, bytes_read);
187				return;
188			}
189			if (filesize < 512)
190				bytes_read = filesize;
191			if (f != NULL) {
192				if (fwrite(buff, 1, bytes_read, f)
193				    != bytes_read)
194				{
195					fprintf(stderr, "Failed write\n");
196					fclose(f);
197					f = NULL;
198				}
199			}
200			filesize -= bytes_read;
201		}
202		if (f != NULL) {
203			fclose(f);
204			f = NULL;
205		}
206	}
207}
208
209int
210main(int argc, char **argv)
211{
212	FILE *a;
213
214	++argv; /* Skip program name */
215	for ( ;*argv != NULL; ++argv) {
216		a = fopen(*argv, "r");
217		if (a == NULL)
218			fprintf(stderr, "Unable to open %s\n", *argv);
219		else {
220			untar(a, *argv);
221			fclose(a);
222		}
223	}
224	return (0);
225}
226