rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / pidgin / win32 / untar.c
blobbbe677dfbc5857d128006cba310793540d384e91
1 /* untar.c */
3 /*#define VERSION "1.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.
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).
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.
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.
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>
50 * April 4, 2003
51 * Changes: Stripped out gz compression code, added better interface for
52 * untar.
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
63 #ifdef _WEAK_POSIX
64 # ifndef _POSIX_SOURCE
65 # define _POSIX_SOURCE
66 # endif
67 #endif
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>
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;
98 typedef struct
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 */
116 } tar_t;
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 */
145 FILE *fp;
146 int i;
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);
152 return NULL;
155 /* first try creating it the easy way */
156 fp = g_fopen(name, CONVERT ? "w" : "wb");
157 if (fp)
158 return fp;
160 /* Else try making all of its directories, and then try creating
161 * the file again.
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.
169 if (name[i] == '/')
171 name[i] = '\0';
172 (void)g_mkdir(name, 0777);
173 name[i] = '/';
176 fp = g_fopen(name, CONVERT ? "w" : "wb");
177 if (!fp)
178 untar_error("Error opening: %s\n", name);
179 return fp;
182 /* Create a link, or copy a file. If the file is copied (not linked) then
183 * give a warning.
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 */
190 FILE *fpsrc;
191 FILE *fpdst;
192 int c;
194 /* Open the source file. We do this first to make sure it exists */
195 fpsrc = g_fopen(src, "rb");
196 if (!fpsrc)
198 untar_error("Error opening: %s\n", src);
199 return;
202 /* Create the destination file. On POSIX systems, this is just to
203 * make sure the directory path exists.
205 fpdst = createpath(dst);
206 if (!fpdst) {
207 /* error message already given */
208 fclose(fpsrc);
209 return;
212 #ifdef _POSIX_SOURCE
213 # ifndef _WEAK_POSIX
214 /* first try to link it over, instead of copying */
215 fclose(fpdst);
216 g_unlink(dst);
217 if (sym)
219 if (symlink(src, dst))
221 perror(dst);
223 fclose(fpsrc);
224 return;
226 if (!link(src, dst))
228 /* This story had a happy ending */
229 fclose(fpsrc);
230 return;
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)
242 putc(c, fpdst);
244 /* Close the files */
245 fclose(fpsrc);
246 fclose(fpdst);
248 /* Give a warning */
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 */
258 Ulong_t i, j;
259 static Uchar_t mod[TSIZE];
261 if (CONVERT)
263 for (i = j = 0; i < size; i++)
265 /* convert LF to local newline convention */
266 if (blk[i] == LF)
267 mod[j++] = '\n';
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 */
272 else
273 mod[j++] = blk[i];
275 size = j;
276 blk = mod;
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) */
291 long sum;
292 char *scan;
294 /* compute the sum of the first 148 bytes -- everything up to but not
295 * including the checksum field itself.
297 sum = 0L;
298 for (scan = (char *)tblk; scan < tblk->checksum; scan++)
300 sum += (*scan) & 0xff;
301 if (sunny && (*scan & 0x80) != 0)
302 sum -= 256;
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)
314 sum -= 256;
317 return sum;
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 */
328 guint i;
329 tar_t tblk[1];
331 #ifdef _POSIX_SOURCE
332 static mode_t mode; /* file permissions */
333 static struct utimbuf timestamp; /* file timestamp */
334 #endif
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 */
340 if (outsize > TSIZE)
342 /* data block, but not the last one */
343 if (outfp)
344 cvtwrite(blk, (Ulong_t)TSIZE, outfp);
345 outsize -= TSIZE;
347 else if (outsize > 0)
349 /* last data block of current file */
350 if (outfp)
352 cvtwrite(blk, outsize, outfp);
353 fclose(outfp);
354 outfp = NULL;
355 #ifdef _POSIX_SOURCE
356 utime(nbuf, &timestamp);
357 chmod(nbuf, mode);
358 #endif
360 outsize = 0;
362 else if ((tblk)->filename[0] == '\0')
364 /* end-of-archive marker */
365 if (didabs)
366 untar_warning("Removed leading slashes because \"ABSPATH option\" wasn't given.\n");
367 return 1;
369 else
371 /* file header */
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')
379 if (first)
381 untar_error("%s: not a valid tar file\n", inname);
382 return 0;
384 else
386 untar_error("Garbage detected; preceding file may be damaged\n");
387 return 0;
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);
398 else
400 g_strlcpy(nbuf, (tblk)->filename,
401 sizeof (nbuf));
404 /* Possibly strip the drive from the path */
405 if (!ABSPATH) {
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, ':');
411 if (lastcolon) {
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.
421 name = nbuf;
422 if (!ABSPATH && (*name == '/' || *name == '\\'))
423 didabs = 1;
424 for (n2 = nbuf; *name; name++)
426 if (*name == '\\')
427 *name = '/';
428 if (*name != '/'
429 || (ABSPATH && n2 == nbuf)
430 || (n2 != nbuf && n2[-1] != '/'))
431 *n2++ = *name;
433 if (n2 == nbuf)
434 *n2++ = '/';
435 *n2 = '\0';
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))
446 if (!first)
447 untar_error("Garbage detected; preceding file may be damaged\n");
448 untar_error("%s: header has bad checksum for %s\n", inname, nbuf);
449 return 0;
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.
455 first = 0;
457 /* if last character of name is '/' then assume directory */
458 if (*nbuf && nbuf[strlen(nbuf) - 1] == '/')
459 (tblk)->type = '5';
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';
468 #ifdef _POSIX_SOURCE
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';
484 #endif
486 /* list the file */
487 if (VERBOSE)
488 untar_verbose("%c %s",
489 ISREGULAR(*tblk) ? '-' : ("hlcbdp"[(tblk)->type - '1']),
490 nbuf);
491 else if (!QUIET)
492 untar_verbose("%s\n", nbuf);
494 /* if link, then do the link-or-copy thing */
495 if (tblk->type == '1' || tblk->type == '2')
497 if (VERBOSE)
498 untar_verbose(" -> %s\n", tblk->linkto);
499 if (!LISTING)
500 linkorcopy(tblk->linkto, nbuf, tblk->type == '2');
501 outsize = 0L;
502 return 1;
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')
512 char *tmp;
513 if (LISTING)
514 tmp = " directory";
515 #ifdef _POSIX_SOURCE
516 else if (mkdir(nbuf, mode) == 0)
517 #else
518 else if (g_mkdir(nbuf, 0755) == 0)
519 #endif
520 tmp = " created";
521 else
522 tmp = " ignored";
523 if (VERBOSE)
524 untar_verbose("%s\n", tmp);
525 return 1;
528 /* if not a regular file, then skip it */
529 if (!ISREGULAR(*tblk))
531 if (VERBOSE)
532 untar_verbose(" ignored\n");
533 outsize = 0L;
534 return 1;
537 /* print file statistics */
538 if (VERBOSE)
540 untar_verbose(" (%ld byte%s, %ld tape block%s)\n",
541 outsize,
542 outsize == 1 ? "" : "s",
543 (outsize + TSIZE - 1) / TSIZE,
544 (outsize > 0 && outsize <= TSIZE) ? "" : "s");
547 /* if extracting, then try to create the file */
548 if (!LISTING)
549 outfp = createpath(nbuf);
550 else
551 outfp = NULL;
553 /* if file is 0 bytes long, then we're done already! */
554 if (outsize == 0 && outfp)
556 fclose(outfp);
557 #ifdef _POSIX_SOURCE
558 utime(nbuf, &timestamp);
559 chmod(nbuf, mode);
560 #endif
563 return 1;
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) {
570 int ret=1;
571 wchar_t curdir[_MAX_PATH];
572 wchar_t *w_destdir;
573 untarops = options;
574 /* open the archive */
575 inname = filename;
576 infp = g_fopen(filename, "rb");
577 if (!infp)
579 untar_error("Error opening: %s\n", filename);
580 return 0;
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());
588 fclose(infp);
589 return 0;
591 if(!SetCurrentDirectoryW(w_destdir)) {
592 untar_error("Could not set current directory to (error %lu): %s\n", GetLastError(), destdir);
593 fclose(infp);
594 return 0;
595 } else {
596 /* UNCOMPRESSED */
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);
601 fclose(infp);
602 ret=0;
605 if (outsize > 0 && ret) {
606 untar_warning("Last file might be truncated!\n");
607 fclose(outfp);
608 outfp = NULL;
610 if(!SetCurrentDirectoryW(curdir)) {
611 untar_error("Could not set current dir back to original (error %lu).\n", GetLastError());
612 ret=0;
616 g_free(w_destdir);
618 /* close the archive file. */
619 fclose(infp);
621 return ret;