src/win32/untar.c

changeset 14253
b63ebf84c42b
parent 14252
d10dda2777a9
child 14254
77edc7a6191a
equal deleted inserted replaced
14252:d10dda2777a9 14253:b63ebf84c42b
1 /* untar.c */
2
3 /*#define VERSION "1.4"*/
4
5 /* DESCRIPTION:
6 * Untar extracts files from an uncompressed tar archive, or one which
7 * has been compressed with gzip. Usually such archives will have file
8 * names that end with ".tar" or ".tgz" respectively, although untar
9 * doesn't depend on any naming conventions. For a summary of the
10 * command-line options, run untar with no arguments.
11 *
12 * HOW TO COMPILE:
13 * Untar doesn't require any special libraries or compile-time flags.
14 * A simple "cc untar.c -o untar" (or the local equivalent) is
15 * sufficient. Even "make untar" works, without needing a Makefile.
16 * For Microsoft Visual C++, the command is "cl /D_WEAK_POSIX untar.c"
17 * (for 32 bit compilers) or "cl /F 1400 untar.c" (for 16-bit).
18 *
19 * IF YOU SEE COMPILER WARNINGS, THAT'S NORMAL; you can ignore them.
20 * Most of the warnings could be eliminated by adding #include <string.h>
21 * but that isn't portable -- some systems require <strings.h> and
22 * <malloc.h>, for example. Because <string.h> isn't quite portable,
23 * and isn't really necessary in the context of this program, it isn't
24 * included.
25 *
26 * PORTABILITY:
27 * Untar only requires the <stdio.h> header. It uses old-style function
28 * definitions. It opens all files in binary mode. Taken together,
29 * this means that untar should compile & run on just about anything.
30 *
31 * If your system supports the POSIX chmod(2), utime(2), link(2), and
32 * symlink(2) calls, then you may wish to compile with -D_POSIX_SOURCE,
33 * which will enable untar to use those system calls to restore the
34 * timestamp and permissions of the extracted files, and restore links.
35 * (For Linux, _POSIX_SOURCE is always defined.)
36 *
37 * For systems which support some POSIX features but not enough to support
38 * -D_POSIX_SOURCE, you might be able to use -D_WEAK_POSIX. This allows
39 * untar to restore time stamps and file permissions, but not links.
40 * This should work for Microsoft systems, and hopefully others as well.
41 *
42 * AUTHOR & COPYRIGHT INFO:
43 * Written by Steve Kirkendall, kirkenda@cs.pdx.edu
44 * Placed in public domain, 6 October 1995
45 *
46 * Portions derived from inflate.c -- Not copyrighted 1992 by Mark Adler
47 * version c10p1, 10 January 1993
48 *
49 * Altered by Herman Bloggs <hermanator12002@yahoo.com>
50 * April 4, 2003
51 * Changes: Stripped out gz compression code, added better interface for
52 * untar.
53 */
54 #include <windows.h>
55 #include <stdio.h>
56 #include <io.h>
57 #include <string.h>
58 #include <stdlib.h>
59 #ifndef SEEK_SET
60 # define SEEK_SET 0
61 #endif
62
63 #ifdef _WEAK_POSIX
64 # ifndef _POSIX_SOURCE
65 # define _POSIX_SOURCE
66 # endif
67 #endif
68
69 #ifdef _POSIX_SOURCE
70 # include <sys/types.h>
71 # include <sys/stat.h>
72 # include <sys/utime.h>
73 # ifdef _WEAK_POSIX
74 # define mode_t int
75 # else
76 # include <unistd.h>
77 # endif
78 #endif
79 #include "debug.h"
80 #include "untar.h"
81 #include <glib.h>
82
83 #if GLIB_CHECK_VERSION(2,6,0)
84 # include <glib/gstdio.h>
85 #else
86 #define mkdir(a,b) _mkdir((a))
87 #define g_mkdir mkdir
88 #define g_fopen fopen
89 #define g_unlink unlink
90 #endif
91
92 #define untar_error( error, args... ) gaim_debug(GAIM_DEBUG_ERROR, "untar", error, ## args )
93 #define untar_warning( warning, args... ) gaim_debug(GAIM_DEBUG_WARNING, "untar", warning, ## args )
94 #define untar_verbose( args... ) gaim_debug(GAIM_DEBUG_INFO, "untar", ## args )
95
96 #define WSIZE 32768 /* size of decompression buffer */
97 #define TSIZE 512 /* size of a "tape" block */
98 #define CR 13 /* carriage-return character */
99 #define LF 10 /* line-feed character */
100
101 typedef unsigned char Uchar_t;
102 typedef unsigned short Ushort_t;
103 typedef unsigned long Ulong_t;
104
105 typedef struct
106 {
107 char filename[100]; /* 0 name of next file */
108 char mode[8]; /* 100 Permissions and type (octal digits) */
109 char owner[8]; /* 108 Owner ID (ignored) */
110 char group[8]; /* 116 Group ID (ignored) */
111 char size[12]; /* 124 Bytes in file (octal digits) */
112 char mtime[12]; /* 136 Modification time stamp (octal digits)*/
113 char checksum[8]; /* 148 Header checksum (ignored) */
114 char type; /* 156 File type (see below) */
115 char linkto[100]; /* 157 Linked-to name */
116 char brand[8]; /* 257 Identifies tar version (ignored) */
117 char ownername[32]; /* 265 Name of owner (ignored) */
118 char groupname[32]; /* 297 Name of group (ignored) */
119 char devmajor[8]; /* 329 Device major number (ignored) */
120 char defminor[8]; /* 337 Device minor number (ignored) */
121 char prefix[155]; /* 345 Prefix of name (optional) */
122 char RESERVED[12]; /* 500 Pad header size to 512 bytes */
123 } tar_t;
124 #define ISREGULAR(hdr) ((hdr).type < '1' || (hdr).type > '6')
125
126 Uchar_t slide[WSIZE];
127
128 static const char *inname = NULL; /* name of input archive */
129 static FILE *infp = NULL; /* input byte stream */
130 static FILE *outfp = NULL; /* output stream, for file currently being extracted */
131 static Ulong_t outsize = 0; /* number of bytes remainin in file currently being extracted */
132 static char **only = NULL; /* array of filenames to extract/list */
133 static int nonlys = 0; /* number of filenames in "only" array; 0=extract all */
134 static int didabs = 0; /* were any filenames affected by the absence of -p? */
135
136 static untar_opt untarops = 0; /* Untar options */
137
138 /* Options checked during untar process */
139 #define LISTING (untarops & UNTAR_LISTING) /* 1 if listing, 0 if extracting */
140 #define QUIET (untarops & UNTAR_QUIET) /* 1 to write nothing to stdout, 0 for normal chatter */
141 #define VERBOSE (untarops & UNTAR_VERBOSE) /* 1 to write extra information to stdout */
142 #define FORCE (untarops & UNTAR_FORCE) /* 1 to overwrite existing files, 0 to skip them */
143 #define ABSPATH (untarops & UNTAR_ABSPATH) /* 1 to allow leading '/', 0 to strip leading '/' */
144 #define CONVERT (untarops & UNTAR_CONVERT) /* 1 to convert newlines, 0 to leave unchanged */
145
146 /*----------------------------------------------------------------------------*/
147
148 /* create a file for writing. If necessary, create the directories leading up
149 * to that file as well.
150 */
151 static FILE *createpath(name)
152 char *name; /* pathname of file to create */
153 {
154 FILE *fp;
155 int i;
156
157 /* if we aren't allowed to overwrite and this file exists, return NULL */
158 if (!FORCE && access(name, 0) == 0)
159 {
160 untar_warning("%s: exists, will not overwrite without \"FORCE option\"\n", name);
161 return NULL;
162 }
163
164 /* first try creating it the easy way */
165 fp = g_fopen(name, CONVERT ? "w" : "wb");
166 if (fp)
167 return fp;
168
169 /* Else try making all of its directories, and then try creating
170 * the file again.
171 */
172 for (i = 0; name[i]; i++)
173 {
174 /* If this is a slash, then temporarily replace the '/'
175 * with a '\0' and do a mkdir() on the resulting string.
176 * Ignore errors for now.
177 */
178 if (name[i] == '/')
179 {
180 name[i] = '\0';
181 (void)g_mkdir(name, 0777);
182 name[i] = '/';
183 }
184 }
185 fp = g_fopen(name, CONVERT ? "w" : "wb");
186 if (!fp)
187 untar_error("Error opening: %s\n", name);
188 return fp;
189 }
190
191 /* Create a link, or copy a file. If the file is copied (not linked) then
192 * give a warning.
193 */
194 static void linkorcopy(src, dst, sym)
195 char *src; /* name of existing source file */
196 char *dst; /* name of new destination file */
197 int sym; /* use symlink instead of link */
198 {
199 FILE *fpsrc;
200 FILE *fpdst;
201 int c;
202
203 /* Open the source file. We do this first to make sure it exists */
204 fpsrc = g_fopen(src, "rb");
205 if (!fpsrc)
206 {
207 untar_error("Error opening: %s\n", src);
208 return;
209 }
210
211 /* Create the destination file. On POSIX systems, this is just to
212 * make sure the directory path exists.
213 */
214 fpdst = createpath(dst);
215 if (!fpdst)
216 /* error message already given */
217 return;
218
219 #ifdef _POSIX_SOURCE
220 # ifndef _WEAK_POSIX
221 /* first try to link it over, instead of copying */
222 fclose(fpdst);
223 g_unlink(dst);
224 if (sym)
225 {
226 if (symlink(src, dst))
227 {
228 perror(dst);
229 }
230 fclose(fpsrc);
231 return;
232 }
233 if (!link(src, dst))
234 {
235 /* This story had a happy ending */
236 fclose(fpsrc);
237 return;
238 }
239
240 /* Dang. Reopen the destination again */
241 fpdst = g_fopen(dst, "wb");
242 /* This *can't* fail */
243
244 # endif /* _WEAK_POSIX */
245 #endif /* _POSIX_SOURCE */
246
247 /* Copy characters */
248 while ((c = getc(fpsrc)) != EOF)
249 putc(c, fpdst);
250
251 /* Close the files */
252 fclose(fpsrc);
253 fclose(fpdst);
254
255 /* Give a warning */
256 untar_warning("%s: copy instead of link\n", dst);
257 }
258
259 /* This calls fwrite(), possibly after converting CR-LF to LF */
260 static void cvtwrite(blk, size, fp)
261 Uchar_t *blk; /* the block to be written */
262 Ulong_t size; /* number of characters to be written */
263 FILE *fp; /* file to write to */
264 {
265 int i, j;
266 static Uchar_t mod[TSIZE];
267
268 if (CONVERT)
269 {
270 for (i = j = 0; i < size; i++)
271 {
272 /* convert LF to local newline convention */
273 if (blk[i] == LF)
274 mod[j++] = '\n';
275 /* If CR-LF pair, then delete the CR */
276 else if (blk[i] == CR && (i+1 >= size || blk[i+1] == LF))
277 ;
278 /* other characters copied literally */
279 else
280 mod[j++] = blk[i];
281 }
282 size = j;
283 blk = mod;
284 }
285
286 fwrite(blk, (size_t)size, sizeof(Uchar_t), fp);
287 }
288
289
290 /* Compute the checksum of a tar header block, and return it as a long int.
291 * The checksum can be computed using either POSIX rules (unsigned bytes)
292 * or Sun rules (signed bytes).
293 */
294 static long checksum(tblk, sunny)
295 tar_t *tblk; /* buffer containing the tar header block */
296 int sunny; /* Boolean: Sun-style checksums? (else POSIX) */
297 {
298 long sum;
299 char *scan;
300
301 /* compute the sum of the first 148 bytes -- everything up to but not
302 * including the checksum field itself.
303 */
304 sum = 0L;
305 for (scan = (char *)tblk; scan < tblk->checksum; scan++)
306 {
307 sum += (*scan) & 0xff;
308 if (sunny && (*scan & 0x80) != 0)
309 sum -= 256;
310 }
311
312 /* for the 8 bytes of the checksum field, add blanks to the sum */
313 sum += ' ' * sizeof tblk->checksum;
314 scan += sizeof tblk->checksum;
315
316 /* finish counting the sum of the rest of the block */
317 for (; scan < (char *)tblk + sizeof *tblk; scan++)
318 {
319 sum += (*scan) & 0xff;
320 if (sunny && (*scan & 0x80) != 0)
321 sum -= 256;
322 }
323
324 return sum;
325 }
326
327
328
329 /* list files in an archive, and optionally extract them as well */
330 static int untar_block(Uchar_t *blk) {
331 static char nbuf[256];/* storage space for prefix+name, combined */
332 static char *name,*n2;/* prefix and name, combined */
333 static int first = 1;/* Boolean: first block of archive? */
334 long sum; /* checksum for this block */
335 int i;
336 tar_t tblk[1];
337
338 #ifdef _POSIX_SOURCE
339 static mode_t mode; /* file permissions */
340 static struct utimbuf timestamp; /* file timestamp */
341 #endif
342
343 /* make a local copy of the block, and treat it as a tar header */
344 tblk[0] = *(tar_t *)blk;
345
346 /* process each type of tape block differently */
347 if (outsize > TSIZE)
348 {
349 /* data block, but not the last one */
350 if (outfp)
351 cvtwrite(blk, (Ulong_t)TSIZE, outfp);
352 outsize -= TSIZE;
353 }
354 else if (outsize > 0)
355 {
356 /* last data block of current file */
357 if (outfp)
358 {
359 cvtwrite(blk, outsize, outfp);
360 fclose(outfp);
361 outfp = NULL;
362 #ifdef _POSIX_SOURCE
363 utime(nbuf, &timestamp);
364 chmod(nbuf, mode);
365 #endif
366 }
367 outsize = 0;
368 }
369 else if ((tblk)->filename[0] == '\0')
370 {
371 /* end-of-archive marker */
372 if (didabs)
373 untar_warning("Removed leading slashes because \"ABSPATH option\" wasn't given.\n");
374 return 1;
375 }
376 else
377 {
378 /* file header */
379
380 /* half-assed verification -- does it look like header? */
381 if ((tblk)->filename[99] != '\0'
382 || ((tblk)->size[0] < '0'
383 && (tblk)->size[0] != ' ')
384 || (tblk)->size[0] > '9')
385 {
386 if (first)
387 {
388 untar_error("%s: not a valid tar file\n", inname);
389 return 0;
390 }
391 else
392 {
393 untar_error("Garbage detected; preceding file may be damaged\n");
394 return 0;
395 }
396 }
397
398 /* combine prefix and filename */
399 memset(nbuf, 0, sizeof nbuf);
400 name = nbuf;
401 if ((tblk)->prefix[0])
402 {
403 strncpy(name, (tblk)->prefix, sizeof (tblk)->prefix);
404 strcat(name, "/");
405 strncat(name + strlen(name), (tblk)->filename,
406 sizeof (tblk)->filename);
407 }
408 else
409 {
410 strncpy(name, (tblk)->filename,
411 sizeof (tblk)->filename);
412 }
413
414 /* Convert any backslashes to forward slashes, and guard
415 * against doubled-up slashes. (Some DOS versions of "tar"
416 * get this wrong.) Also strip off leading slashes.
417 */
418 if (!ABSPATH && (*name == '/' || *name == '\\'))
419 didabs = 1;
420 for (n2 = nbuf; *name; name++)
421 {
422 if (*name == '\\')
423 *name = '/';
424 if (*name != '/'
425 || (ABSPATH && n2 == nbuf)
426 || (n2 != nbuf && n2[-1] != '/'))
427 *n2++ = *name;
428 }
429 if (n2 == nbuf)
430 *n2++ = '/';
431 *n2 = '\0';
432
433 /* verify the checksum */
434 for (sum = 0L, i = 0; i < sizeof((tblk)->checksum); i++)
435 {
436 if ((tblk)->checksum[i] >= '0'
437 && (tblk)->checksum[i] <= '7')
438 sum = sum * 8 + (tblk)->checksum[i] - '0';
439 }
440 if (sum != checksum(tblk, 0) && sum != checksum(tblk, 1))
441 {
442 if (!first)
443 untar_error("Garbage detected; preceding file may be damaged\n");
444 untar_error("%s: header has bad checksum for %s\n", inname, nbuf);
445 return 0;
446 }
447
448 /* From this point on, we don't care whether this is the first
449 * block or not. Might as well reset the "first" flag now.
450 */
451 first = 0;
452
453 /* if last character of name is '/' then assume directory */
454 if (*nbuf && nbuf[strlen(nbuf) - 1] == '/')
455 (tblk)->type = '5';
456
457 /* convert file size */
458 for (outsize = 0L, i = 0; i < sizeof((tblk)->size); i++)
459 {
460 if ((tblk)->size[i] >= '0' && (tblk)->size[i] <= '7')
461 outsize = outsize * 8 + (tblk)->size[i] - '0';
462 }
463
464 #ifdef _POSIX_SOURCE
465 /* convert file timestamp */
466 for (timestamp.modtime=0L, i=0; i < sizeof((tblk)->mtime); i++)
467 {
468 if ((tblk)->mtime[i] >= '0' && (tblk)->mtime[i] <= '7')
469 timestamp.modtime = timestamp.modtime * 8
470 + (tblk)->mtime[i] - '0';
471 }
472 timestamp.actime = timestamp.modtime;
473
474 /* convert file permissions */
475 for (mode = i = 0; i < sizeof((tblk)->mode); i++)
476 {
477 if ((tblk)->mode[i] >= '0' && (tblk)->mode[i] <= '7')
478 mode = mode * 8 + (tblk)->mode[i] - '0';
479 }
480 #endif
481
482 /* If we have an "only" list, and this file isn't in it,
483 * then skip it.
484 */
485 if (nonlys > 0)
486 {
487 for (i = 0;
488 i < nonlys
489 && strcmp(only[i], nbuf)
490 && (strncmp(only[i], nbuf, strlen(only[i]))
491 || nbuf[strlen(only[i])] != '/');
492 i++)
493 {
494 }
495 if (i >= nonlys)
496 {
497 outfp = NULL;
498 return 1;
499 }
500 }
501
502 /* list the file */
503 if (VERBOSE)
504 untar_verbose("%c %s",
505 ISREGULAR(*tblk) ? '-' : ("hlcbdp"[(tblk)->type - '1']),
506 nbuf);
507 else if (!QUIET)
508 untar_verbose("%s\n", nbuf);
509
510 /* if link, then do the link-or-copy thing */
511 if (tblk->type == '1' || tblk->type == '2')
512 {
513 if (VERBOSE)
514 untar_verbose(" -> %s\n", tblk->linkto);
515 if (!LISTING)
516 linkorcopy(tblk->linkto, nbuf, tblk->type == '2');
517 outsize = 0L;
518 return 1;
519 }
520
521 /* If directory, then make a weak attempt to create it.
522 * Ideally we would do the "create path" thing, but that
523 * seems like more trouble than it's worth since traditional
524 * tar archives don't contain directories anyway.
525 */
526 if (tblk->type == '5')
527 {
528 if (LISTING)
529 n2 = " directory";
530 #ifdef _POSIX_SOURCE
531 else if (mkdir(nbuf, mode) == 0)
532 #else
533 else if (g_mkdir(nbuf, 0755) == 0)
534 #endif
535 n2 = " created";
536 else
537 n2 = " ignored";
538 if (VERBOSE)
539 untar_verbose("%s\n", n2);
540 return 1;
541 }
542
543 /* if not a regular file, then skip it */
544 if (!ISREGULAR(*tblk))
545 {
546 if (VERBOSE)
547 untar_verbose(" ignored\n");
548 outsize = 0L;
549 return 1;
550 }
551
552 /* print file statistics */
553 if (VERBOSE)
554 {
555 untar_verbose(" (%ld byte%s, %ld tape block%s)\n",
556 outsize,
557 outsize == 1 ? "" : "s",
558 (outsize + TSIZE - 1) / TSIZE,
559 (outsize > 0 && outsize <= TSIZE) ? "" : "s");
560 }
561
562 /* if extracting, then try to create the file */
563 if (!LISTING)
564 outfp = createpath(nbuf);
565 else
566 outfp = NULL;
567
568 /* if file is 0 bytes long, then we're done already! */
569 if (outsize == 0 && outfp)
570 {
571 fclose(outfp);
572 #ifdef _POSIX_SOURCE
573 utime(nbuf, &timestamp);
574 chmod(nbuf, mode);
575 #endif
576 }
577 }
578 return 1;
579 }
580
581 /* Process an archive file. This involves reading the blocks one at a time
582 * and passing them to a untar() function.
583 */
584 int untar(const char *filename, const char* destdir, untar_opt options) {
585 int ret=1;
586 char curdir[_MAX_PATH];
587 untarops = options;
588 /* open the archive */
589 inname = filename;
590 infp = g_fopen(filename, "rb");
591 if (!infp)
592 {
593 untar_error("Error opening: %s\n", filename);
594 return 0;
595 }
596
597 /* Set current directory */
598 if(!GetCurrentDirectory(_MAX_PATH, curdir)) {
599 untar_error("Could not get current directory (error %d).\n", GetLastError());
600 fclose(infp);
601 return 0;
602 }
603 if(!SetCurrentDirectory(destdir)) {
604 untar_error("Could not set current directory to (error %d): %s\n", GetLastError(), destdir);
605 fclose(infp);
606 return 0;
607 } else {
608 /* UNCOMPRESSED */
609 /* send each block to the untar_block() function */
610 while (fread(slide, 1, TSIZE, infp) == TSIZE) {
611 if(!untar_block(slide)) {
612 untar_error("untar failure: %s\n", filename);
613 fclose(infp);
614 ret=0;
615 }
616 }
617 if (outsize > 0 && ret) {
618 untar_warning("Last file might be truncated!\n");
619 fclose(outfp);
620 outfp = NULL;
621 }
622 if(!SetCurrentDirectory(curdir)) {
623 untar_error("Could not set current dir back to original (error %d).\n", GetLastError());
624 ret=0;
625 }
626 }
627
628 /* close the archive file. */
629 fclose(infp);
630
631 return ret;
632 }
633

mercurial