3 /*#define VERSION "1.4"*/
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.
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).
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
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.
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.)
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.
42 * AUTHOR & COPYRIGHT INFO:
43 * Written by Steve Kirkendall, kirkenda@cs.pdx.edu
44 * Placed in public domain, 6 October 1995
46 * Portions derived from inflate.c -- Not copyrighted 1992 by Mark Adler
47 * version c10p1, 10 January 1993
49 * Altered by Herman Bloggs <hermanator12002@yahoo.com>
51 * Changes: Stripped out gz compression code, added better interface for
64 # ifndef _POSIX_SOURCE
65 # define _POSIX_SOURCE
70 # include <sys/types.h>
71 # include <sys/stat.h>
72 # include <sys/utime.h>
83 #include <glib/gstdio.h>
85 #define untar_error( error, args... ) purple_debug(PURPLE_DEBUG_ERROR, "untar", error, ## args )
86 #define untar_warning( warning, args... ) purple_debug(PURPLE_DEBUG_WARNING, "untar", warning, ## args )
87 #define untar_verbose( args... ) purple_debug(PURPLE_DEBUG_INFO, "untar", ## args )
89 #define WSIZE 32768 /* size of decompression buffer */
90 #define TSIZE 512 /* size of a "tape" block */
91 #define CR 13 /* carriage-return character */
92 #define LF 10 /* line-feed character */
94 typedef unsigned char Uchar_t
;
95 typedef unsigned short Ushort_t
;
96 typedef unsigned long Ulong_t
;
100 char filename
[100]; /* 0 name of next file */
101 char mode
[8]; /* 100 Permissions and type (octal digits) */
102 char owner
[8]; /* 108 Owner ID (ignored) */
103 char group
[8]; /* 116 Group ID (ignored) */
104 char size
[12]; /* 124 Bytes in file (octal digits) */
105 char mtime
[12]; /* 136 Modification time stamp (octal digits)*/
106 char checksum
[8]; /* 148 Header checksum (ignored) */
107 char type
; /* 156 File type (see below) */
108 char linkto
[100]; /* 157 Linked-to name */
109 char brand
[8]; /* 257 Identifies tar version (ignored) */
110 char ownername
[32]; /* 265 Name of owner (ignored) */
111 char groupname
[32]; /* 297 Name of group (ignored) */
112 char devmajor
[8]; /* 329 Device major number (ignored) */
113 char defminor
[8]; /* 337 Device minor number (ignored) */
114 char prefix
[155]; /* 345 Prefix of name (optional) */
115 char RESERVED
[12]; /* 500 Pad header size to 512 bytes */
117 #define ISREGULAR(hdr) ((hdr).type < '1' || (hdr).type > '6')
119 Uchar_t slide
[WSIZE
];
121 static const char *inname
= NULL
; /* name of input archive */
122 static FILE *infp
= NULL
; /* input byte stream */
123 static FILE *outfp
= NULL
; /* output stream, for file currently being extracted */
124 static Ulong_t outsize
= 0; /* number of bytes remainin in file currently being extracted */
125 static int didabs
= 0; /* were any filenames affected by the absence of -p? */
127 static untar_opt untarops
= 0; /* Untar options */
129 /* Options checked during untar process */
130 #define LISTING (untarops & UNTAR_LISTING) /* 1 if listing, 0 if extracting */
131 #define QUIET (untarops & UNTAR_QUIET) /* 1 to write nothing to stdout, 0 for normal chatter */
132 #define VERBOSE (untarops & UNTAR_VERBOSE) /* 1 to write extra information to stdout */
133 #define FORCE (untarops & UNTAR_FORCE) /* 1 to overwrite existing files, 0 to skip them */
134 #define ABSPATH (untarops & UNTAR_ABSPATH) /* 1 to allow leading '/', 0 to strip leading '/' */
135 #define CONVERT (untarops & UNTAR_CONVERT) /* 1 to convert newlines, 0 to leave unchanged */
137 /*----------------------------------------------------------------------------*/
139 /* create a file for writing. If necessary, create the directories leading up
140 * to that file as well.
142 static FILE *createpath(name
)
143 char *name
; /* pathname of file to create */
148 /* if we aren't allowed to overwrite and this file exists, return NULL */
149 if (!FORCE
&& access(name
, 0) == 0)
151 untar_warning("%s: exists, will not overwrite without \"FORCE option\"\n", name
);
155 /* first try creating it the easy way */
156 fp
= g_fopen(name
, CONVERT
? "w" : "wb");
160 /* Else try making all of its directories, and then try creating
163 for (i
= 0; name
[i
]; i
++)
165 /* If this is a slash, then temporarily replace the '/'
166 * with a '\0' and do a mkdir() on the resulting string.
167 * Ignore errors for now.
172 (void)g_mkdir(name
, 0777);
176 fp
= g_fopen(name
, CONVERT
? "w" : "wb");
178 untar_error("Error opening: %s\n", name
);
182 /* Create a link, or copy a file. If the file is copied (not linked) then
185 static void linkorcopy(src
, dst
, sym
)
186 char *src
; /* name of existing source file */
187 char *dst
; /* name of new destination file */
188 int sym
; /* use symlink instead of link */
194 /* Open the source file. We do this first to make sure it exists */
195 fpsrc
= g_fopen(src
, "rb");
198 untar_error("Error opening: %s\n", src
);
202 /* Create the destination file. On POSIX systems, this is just to
203 * make sure the directory path exists.
205 fpdst
= createpath(dst
);
207 /* error message already given */
214 /* first try to link it over, instead of copying */
219 if (symlink(src
, dst
))
228 /* This story had a happy ending */
233 /* Dang. Reopen the destination again */
234 fpdst
= g_fopen(dst
, "wb");
235 /* This *can't* fail */
237 # endif /* _WEAK_POSIX */
238 #endif /* _POSIX_SOURCE */
240 /* Copy characters */
241 while ((c
= getc(fpsrc
)) != EOF
)
244 /* Close the files */
249 untar_warning("%s: copy instead of link\n", dst
);
252 /* This calls fwrite(), possibly after converting CR-LF to LF */
253 static void cvtwrite(blk
, size
, fp
)
254 Uchar_t
*blk
; /* the block to be written */
255 Ulong_t size
; /* number of characters to be written */
256 FILE *fp
; /* file to write to */
259 static Uchar_t mod
[TSIZE
];
263 for (i
= j
= 0; i
< size
; i
++)
265 /* convert LF to local newline convention */
268 /* If CR-LF pair, then delete the CR */
269 else if (blk
[i
] == CR
&& (i
+1 >= size
|| blk
[i
+1] == LF
))
271 /* other characters copied literally */
279 fwrite(blk
, (size_t)size
, sizeof(Uchar_t
), fp
);
283 /* Compute the checksum of a tar header block, and return it as a long int.
284 * The checksum can be computed using either POSIX rules (unsigned bytes)
285 * or Sun rules (signed bytes).
287 static long checksum(tblk
, sunny
)
288 tar_t
*tblk
; /* buffer containing the tar header block */
289 int sunny
; /* Boolean: Sun-style checksums? (else POSIX) */
294 /* compute the sum of the first 148 bytes -- everything up to but not
295 * including the checksum field itself.
298 for (scan
= (char *)tblk
; scan
< tblk
->checksum
; scan
++)
300 sum
+= (*scan
) & 0xff;
301 if (sunny
&& (*scan
& 0x80) != 0)
305 /* for the 8 bytes of the checksum field, add blanks to the sum */
306 sum
+= ' ' * sizeof tblk
->checksum
;
307 scan
+= sizeof tblk
->checksum
;
309 /* finish counting the sum of the rest of the block */
310 for (; scan
< (char *)tblk
+ sizeof *tblk
; scan
++)
312 sum
+= (*scan
) & 0xff;
313 if (sunny
&& (*scan
& 0x80) != 0)
322 /* list files in an archive, and optionally extract them as well */
323 static int untar_block(Uchar_t
*blk
) {
324 static char nbuf
[4096];/* storage space for prefix+name, combined */
325 static char *name
,*n2
;/* prefix and name, combined */
326 static int first
= 1;/* Boolean: first block of archive? */
327 long sum
; /* checksum for this block */
332 static mode_t mode
; /* file permissions */
333 static struct utimbuf timestamp
; /* file timestamp */
336 /* make a local copy of the block, and treat it as a tar header */
337 tblk
[0] = *(tar_t
*)blk
;
339 /* process each type of tape block differently */
342 /* data block, but not the last one */
344 cvtwrite(blk
, (Ulong_t
)TSIZE
, outfp
);
347 else if (outsize
> 0)
349 /* last data block of current file */
352 cvtwrite(blk
, outsize
, outfp
);
356 utime(nbuf
, ×tamp
);
362 else if ((tblk
)->filename
[0] == '\0')
364 /* end-of-archive marker */
366 untar_warning("Removed leading slashes because \"ABSPATH option\" wasn't given.\n");
373 /* half-assed verification -- does it look like header? */
374 if ((tblk
)->filename
[99] != '\0'
375 || ((tblk
)->size
[0] < '0'
376 && (tblk
)->size
[0] != ' ')
377 || (tblk
)->size
[0] > '9')
381 untar_error("%s: not a valid tar file\n", inname
);
386 untar_error("Garbage detected; preceding file may be damaged\n");
391 /* combine prefix and filename */
392 memset(nbuf
, 0, sizeof nbuf
);
393 if ((tblk
)->prefix
[0])
395 snprintf(nbuf
, sizeof(nbuf
), "%s/%s",
396 (tblk
)->prefix
, (tblk
)->filename
);
400 g_strlcpy(nbuf
, (tblk
)->filename
,
404 /* Possibly strip the drive from the path */
406 /* If the path contains a colon, assume everything before the
407 * colon is intended to be a drive name and ignore it. This
408 * should be just a single drive letter, but it should be safe
409 * to drop it even if it's longer. */
410 const char *lastcolon
= strrchr(nbuf
, ':');
412 memmove(nbuf
, lastcolon
, strlen(lastcolon
) + 1);
413 didabs
= 1; /* Path was changed from absolute to relative */
417 /* Convert any backslashes to forward slashes, and guard
418 * against doubled-up slashes. (Some DOS versions of "tar"
419 * get this wrong.) Also strip off leading slashes.
422 if (!ABSPATH
&& (*name
== '/' || *name
== '\\'))
424 for (n2
= nbuf
; *name
; name
++)
429 || (ABSPATH
&& n2
== nbuf
)
430 || (n2
!= nbuf
&& n2
[-1] != '/'))
437 /* verify the checksum */
438 for (sum
= 0L, i
= 0; i
< sizeof((tblk
)->checksum
); i
++)
440 if ((tblk
)->checksum
[i
] >= '0'
441 && (tblk
)->checksum
[i
] <= '7')
442 sum
= sum
* 8 + (tblk
)->checksum
[i
] - '0';
444 if (sum
!= checksum(tblk
, 0) && sum
!= checksum(tblk
, 1))
447 untar_error("Garbage detected; preceding file may be damaged\n");
448 untar_error("%s: header has bad checksum for %s\n", inname
, nbuf
);
452 /* From this point on, we don't care whether this is the first
453 * block or not. Might as well reset the "first" flag now.
457 /* if last character of name is '/' then assume directory */
458 if (*nbuf
&& nbuf
[strlen(nbuf
) - 1] == '/')
461 /* convert file size */
462 for (outsize
= 0L, i
= 0; i
< sizeof((tblk
)->size
); i
++)
464 if ((tblk
)->size
[i
] >= '0' && (tblk
)->size
[i
] <= '7')
465 outsize
= outsize
* 8 + (tblk
)->size
[i
] - '0';
469 /* convert file timestamp */
470 for (timestamp
.modtime
=0L, i
=0; i
< sizeof((tblk
)->mtime
); i
++)
472 if ((tblk
)->mtime
[i
] >= '0' && (tblk
)->mtime
[i
] <= '7')
473 timestamp
.modtime
= timestamp
.modtime
* 8
474 + (tblk
)->mtime
[i
] - '0';
476 timestamp
.actime
= timestamp
.modtime
;
478 /* convert file permissions */
479 for (mode
= i
= 0; i
< sizeof((tblk
)->mode
); i
++)
481 if ((tblk
)->mode
[i
] >= '0' && (tblk
)->mode
[i
] <= '7')
482 mode
= mode
* 8 + (tblk
)->mode
[i
] - '0';
488 untar_verbose("%c %s",
489 ISREGULAR(*tblk
) ? '-' : ("hlcbdp"[(tblk
)->type
- '1']),
492 untar_verbose("%s\n", nbuf
);
494 /* if link, then do the link-or-copy thing */
495 if (tblk
->type
== '1' || tblk
->type
== '2')
498 untar_verbose(" -> %s\n", tblk
->linkto
);
500 linkorcopy(tblk
->linkto
, nbuf
, tblk
->type
== '2');
505 /* If directory, then make a weak attempt to create it.
506 * Ideally we would do the "create path" thing, but that
507 * seems like more trouble than it's worth since traditional
508 * tar archives don't contain directories anyway.
510 if (tblk
->type
== '5')
516 else if (mkdir(nbuf
, mode
) == 0)
518 else if (g_mkdir(nbuf
, 0755) == 0)
524 untar_verbose("%s\n", tmp
);
528 /* if not a regular file, then skip it */
529 if (!ISREGULAR(*tblk
))
532 untar_verbose(" ignored\n");
537 /* print file statistics */
540 untar_verbose(" (%ld byte%s, %ld tape block%s)\n",
542 outsize
== 1 ? "" : "s",
543 (outsize
+ TSIZE
- 1) / TSIZE
,
544 (outsize
> 0 && outsize
<= TSIZE
) ? "" : "s");
547 /* if extracting, then try to create the file */
549 outfp
= createpath(nbuf
);
553 /* if file is 0 bytes long, then we're done already! */
554 if (outsize
== 0 && outfp
)
558 utime(nbuf
, ×tamp
);
566 /* Process an archive file. This involves reading the blocks one at a time
567 * and passing them to a untar() function.
569 int untar(const char *filename
, const char* destdir
, untar_opt options
) {
571 wchar_t curdir
[_MAX_PATH
];
574 /* open the archive */
576 infp
= g_fopen(filename
, "rb");
579 untar_error("Error opening: %s\n", filename
);
583 w_destdir
= g_utf8_to_utf16(destdir
, -1, NULL
, NULL
, NULL
);
585 /* Set current directory */
586 if(!GetCurrentDirectoryW(_MAX_PATH
, curdir
)) {
587 untar_error("Could not get current directory (error %lu).\n", GetLastError());
591 if(!SetCurrentDirectoryW(w_destdir
)) {
592 untar_error("Could not set current directory to (error %lu): %s\n", GetLastError(), destdir
);
597 /* send each block to the untar_block() function */
598 while (fread(slide
, 1, TSIZE
, infp
) == TSIZE
) {
599 if(!untar_block(slide
)) {
600 untar_error("untar failure: %s\n", filename
);
605 if (outsize
> 0 && ret
) {
606 untar_warning("Last file might be truncated!\n");
610 if(!SetCurrentDirectoryW(curdir
)) {
611 untar_error("Could not set current dir back to original (error %lu).\n", GetLastError());
618 /* close the archive file. */