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