Fix tarball creation. In an attempt to accomodate the whiners
[oweals/busybox.git] / tar.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini tar implementation for busybox 
4  *
5  * Note, that as of BusyBox-0.43, tar has been completely rewritten from the
6  * ground up.  It still has remnents of the old code lying about, but it is
7  * very different now (i.e. cleaner, less global variables, etc)
8  *
9  * Copyright (C) 2000 by Lineo, inc.
10  * Written by Erik Andersen <andersen@lineo.com>, <andersee@debian.org>
11  *
12  * Based in part in the tar implementation in sash
13  *  Copyright (c) 1999 by David I. Bell
14  *  Permission is granted to use, distribute, or modify this source,
15  *  provided that this copyright notice remains intact.
16  *  Permission to distribute sash derived code under the GPL has been granted.
17  *
18  * Based in part on the tar implementation from busybox-0.28
19  *  Copyright (C) 1995 Bruce Perens
20  *  This is free software under the GNU General Public License.
21  *
22  * This program is free software; you can redistribute it and/or modify
23  * it under the terms of the GNU General Public License as published by
24  * the Free Software Foundation; either version 2 of the License, or
25  * (at your option) any later version.
26  *
27  * This program is distributed in the hope that it will be useful,
28  * but WITHOUT ANY WARRANTY; without even the implied warranty of
29  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
30  * General Public License for more details.
31  *
32  * You should have received a copy of the GNU General Public License
33  * along with this program; if not, write to the Free Software
34  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35  *
36  */
37
38
39 #include "internal.h"
40 #define BB_DECLARE_EXTERN
41 #define bb_need_io_error
42 #include "messages.c"
43 #include <stdio.h>
44 #include <dirent.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <signal.h>
48 #include <time.h>
49 #include <utime.h>
50 #include <sys/types.h>
51 #include <sys/sysmacros.h>
52
53
54 static const char tar_usage[] =
55 #ifdef BB_FEATURE_TAR_CREATE
56         "tar -[cxtvO] "
57 #else
58         "tar -[xtvO] "
59 #endif
60 #if defined BB_FEATURE_TAR_EXCLUDE
61         "[--exclude File] "
62 #endif
63         "[-f tarFile] [FILE] ...\n\n"
64         "Create, extract, or list files from a tar file.  Note that\n"
65         "this version of tar treats hard links as separate files.\n\n"
66         "Main operation mode:\n"
67 #ifdef BB_FEATURE_TAR_CREATE
68         "\tc\t\tcreate\n"
69 #endif
70         "\tx\t\textract\n"
71         "\tt\t\tlist\n"
72         "\nFile selection:\n"
73         "\tf\t\tname of tarfile or \"-\" for stdin\n"
74         "\tO\t\textract to stdout\n"
75 #if defined BB_FEATURE_TAR_EXCLUDE
76         "\t--exclude\tfile to exclude\n"
77 #endif
78         "\nInformative output:\n"
79         "\tv\t\tverbosely list files processed\n"
80         ;
81
82 /* Tar file constants  */
83 #ifndef MAJOR
84 #define MAJOR(dev) (((dev)>>8)&0xff)
85 #define MINOR(dev) ((dev)&0xff)
86 #endif
87
88
89 /* POSIX tar Header Block, from POSIX 1003.1-1990  */
90 struct TarHeader
91 {
92                                 /* byte offset */
93         char name[100];               /*   0-99 */
94         char mode[8];                 /* 100-107 */
95         char uid[8];                  /* 108-115 */
96         char gid[8];                  /* 116-123 */
97         char size[12];                /* 124-135 */
98         char mtime[12];               /* 136-147 */
99         char chksum[8];               /* 148-155 */
100         char typeflag;                /* 156-156 */
101         char linkname[100];           /* 157-256 */
102         char magic[6];                /* 257-262 */
103         char version[2];              /* 263-264 */
104         char uname[32];               /* 265-296 */
105         char gname[32];               /* 297-328 */
106         char devmajor[8];             /* 329-336 */
107         char devminor[8];             /* 337-344 */
108         char prefix[155];             /* 345-499 */
109         char padding[12];             /* 500-512 (pad to exactly the TAR_BLOCK_SIZE) */
110 };
111 typedef struct TarHeader TarHeader;
112
113
114 /* A few useful constants */
115 #define TAR_MAGIC          "ustar"        /* ustar and a null */
116 #define TAR_VERSION        "  "           /* Be compatable with GNU tar format */
117 #define TAR_MAGIC_LEN       6
118 #define TAR_VERSION_LEN     2
119 #define TAR_BLOCK_SIZE      512
120
121 /* A nice enum with all the possible tar file content types */
122 enum TarFileType 
123 {
124         REGTYPE  = '0',            /* regular file */
125         REGTYPE0 = '\0',           /* regular file (ancient bug compat)*/
126         LNKTYPE  = '1',            /* hard link */
127         SYMTYPE  = '2',            /* symbolic link */
128         CHRTYPE  = '3',            /* character special */
129         BLKTYPE  = '4',            /* block special */
130         DIRTYPE  = '5',            /* directory */
131         FIFOTYPE = '6',            /* FIFO special */
132         CONTTYPE = '7',            /* reserved */
133 };
134 typedef enum TarFileType TarFileType;
135
136 /* This struct ignores magic, non-numeric user name, 
137  * non-numeric group name, and the checksum, since
138  * these are all ignored by BusyBox tar. */ 
139 struct TarInfo
140 {
141         int              tarFd;          /* An open file descriptor for reading from the tarball */
142         char *           name;           /* File name */
143         mode_t           mode;           /* Unix mode, including device bits. */
144         uid_t            uid;            /* Numeric UID */
145         gid_t            gid;            /* Numeric GID */
146         size_t           size;           /* Size of file */
147         time_t           mtime;          /* Last-modified time */
148         enum TarFileType type;           /* Regular, directory, link, etc */
149         char *           linkname;       /* Name for symbolic and hard links */
150         long             devmajor;       /* Major number for special device */
151         long             devminor;       /* Minor number for special device */
152 };
153 typedef struct TarInfo TarInfo;
154
155 /* Local procedures to restore files from a tar file.  */
156 static int readTarFile(const char* tarName, int extractFlag, int listFlag, 
157                 int tostdoutFlag, int verboseFlag, char** excludeList);
158
159
160
161 #ifdef BB_FEATURE_TAR_CREATE
162 /* Local procedures to save files into a tar file.  */
163 static int writeTarFile(const char* tarName, int tostdoutFlag, 
164                 int verboseFlag, int argc, char **argv, char** excludeList);
165 #endif
166
167
168 extern int tar_main(int argc, char **argv)
169 {
170         char** excludeList=NULL;
171 #if defined BB_FEATURE_TAR_EXCLUDE
172         int excludeListSize=0;
173 #endif
174         const char *tarName=NULL;
175         int listFlag     = FALSE;
176         int extractFlag  = FALSE;
177         int createFlag   = FALSE;
178         int verboseFlag  = FALSE;
179         int tostdoutFlag = FALSE;
180         int stopIt;
181
182         if (argc <= 1)
183                 usage(tar_usage);
184
185         /* Parse any options */
186         while (--argc > 0 && **(++argv) == '-') {
187                 stopIt=FALSE;
188                 while (stopIt==FALSE && *(++(*argv))) {
189                         switch (**argv) {
190                                 case 'f':
191                                         if (--argc == 0) {
192                                                 fatalError( "Option requires an argument: No file specified\n");
193                                         }
194                                         if (tarName != NULL)
195                                                 fatalError( "Only one 'f' option allowed\n");
196                                         tarName = *(++argv);
197                                         if (tarName == NULL)
198                                                 fatalError( "Option requires an argument: No file specified\n");
199                                         stopIt=TRUE;
200                                         break;
201
202                                 case 't':
203                                         if (extractFlag == TRUE || createFlag == TRUE)
204                                                 goto flagError;
205                                         listFlag = TRUE;
206                                         break;
207
208                                 case 'x':
209                                         if (listFlag == TRUE || createFlag == TRUE)
210                                                 goto flagError;
211                                         extractFlag = TRUE;
212                                         break;
213                                 case 'c':
214                                         if (extractFlag == TRUE || listFlag == TRUE)
215                                                 goto flagError;
216                                         createFlag = TRUE;
217                                         break;
218
219                                 case 'v':
220                                         verboseFlag = TRUE;
221                                         break;
222
223                                 case 'O':
224                                         tostdoutFlag = TRUE;
225                                         tarName = "-";
226                                         break;
227                                 case '-':
228 #if defined BB_FEATURE_TAR_EXCLUDE
229                                         if (strcmp(*argv, "-exclude")==0) {
230                                                 if (--argc == 0) {
231                                                         fatalError( "Option requires an argument: No file specified\n");
232                                                 }
233                                                 excludeList=realloc( excludeList, sizeof(char**) * (excludeListSize+2));
234                                                 excludeList[excludeListSize] = *(++argv);
235                                                 /* Remove leading "/"s */
236                                                 if (*excludeList[excludeListSize] =='/') {
237                                                         excludeList[excludeListSize] = (excludeList[excludeListSize])+1;
238                                                 }
239                                                 if (excludeList[excludeListSize++] == NULL)
240                                                         fatalError( "Option requires an argument: No file specified\n");
241                                                 /* Tack a NULL onto the end of the list */
242                                                 excludeList[excludeListSize] = NULL;
243                                                 stopIt=TRUE;
244                                                 break;
245                                         }
246 #endif
247                                         break;
248
249                                 default:
250                                         fatalError( "Unknown tar flag '%c'\n" 
251                                                         "Try `tar --help' for more information\n", **argv);
252                         }
253                 }
254         }
255
256         /* 
257          * Do the correct type of action supplying the rest of the
258          * command line arguments as the list of files to process.
259          */
260         if (createFlag == TRUE) {
261 #ifndef BB_FEATURE_TAR_CREATE
262                 fatalError( "This version of tar was not compiled with tar creation support.\n");
263 #else
264                 exit(writeTarFile(tarName, tostdoutFlag, verboseFlag, argc, argv, excludeList));
265 #endif
266         } else {
267                 exit(readTarFile(tarName, extractFlag, listFlag, tostdoutFlag, verboseFlag, excludeList));
268         }
269
270   flagError:
271         fatalError( "Exactly one of 'c', 'x' or 't' must be specified\n");
272 }
273                                         
274 static void
275 fixUpPermissions(TarInfo *header)
276 {
277         struct utimbuf t;
278         /* Now set permissions etc for the new file */
279         chown(header->name, header->uid, header->gid);
280         chmod(header->name, header->mode);
281         /* Reset the time */
282         t.actime = time(0);
283         t.modtime = header->mtime;
284         utime(header->name, &t);
285 }
286                                 
287 static int
288 tarExtractRegularFile(TarInfo *header, int extractFlag, int tostdoutFlag)
289 {
290         size_t  writeSize;
291         size_t  readSize;
292         size_t  actualWriteSz;
293         char    buffer[BUFSIZ];
294         size_t  size = header->size;
295         int outFd=fileno(stdout);
296
297         /* Open the file to be written, if a file is supposed to be written */
298         if (extractFlag==TRUE && tostdoutFlag==FALSE) {
299                 if ((outFd=open(header->name, O_CREAT|O_TRUNC|O_WRONLY, header->mode & ~S_IFMT)) < 0)
300                         errorMsg(io_error, header->name, strerror(errno)); 
301                 /* Create the path to the file, just in case it isn't there...
302                  * This should not screw up path permissions or anything. */
303                 createPath(header->name, 0777);
304         }
305
306         /* Write out the file, if we are supposed to be doing that */
307         while ( size > 0 ) {
308                 actualWriteSz=0;
309                 if ( size > sizeof(buffer) )
310                         writeSize = readSize = sizeof(buffer);
311                 else {
312                         int mod = size % 512;
313                         if ( mod != 0 )
314                                 readSize = size + (512 - mod);
315                         else
316                                 readSize = size;
317                         writeSize = size;
318                 }
319                 if ( (readSize = fullRead(header->tarFd, buffer, readSize)) <= 0 ) {
320                         /* Tarball seems to have a problem */
321                         errorMsg("tar: Unexpected EOF in archive\n"); 
322                         return( FALSE);
323                 }
324                 if ( readSize < writeSize )
325                         writeSize = readSize;
326
327                 /* Write out the file, if we are supposed to be doing that */
328                 if (extractFlag==TRUE) {
329
330                         if ((actualWriteSz=fullWrite(outFd, buffer, writeSize)) != writeSize ) {
331                                 /* Output file seems to have a problem */
332                                 errorMsg(io_error, header->name, strerror(errno)); 
333                                 return( FALSE);
334                         }
335                 } else {
336                         actualWriteSz=writeSize;
337                 }
338
339                 size -= actualWriteSz;
340         }
341
342         /* Now we are done writing the file out, so try 
343          * and fix up the permissions and whatnot */
344         if (extractFlag==TRUE && tostdoutFlag==FALSE) {
345                 close(outFd);
346                 fixUpPermissions(header);
347         }
348         return( TRUE);
349 }
350
351 static int
352 tarExtractDirectory(TarInfo *header, int extractFlag, int tostdoutFlag)
353 {
354
355         if (extractFlag==FALSE || tostdoutFlag==TRUE)
356                 return( TRUE);
357
358         if (createPath(header->name, header->mode) != TRUE) {
359                 errorMsg("tar: %s: Cannot mkdir: %s\n", 
360                                 header->name, strerror(errno)); 
361                 return( FALSE);
362         }
363         /* make the final component, just in case it was
364          * omitted by createPath() (which will skip the
365          * directory if it doesn't have a terminating '/') */
366         if (mkdir(header->name, header->mode) == 0) {
367                 fixUpPermissions(header);
368         }
369         return( TRUE);
370 }
371
372 static int
373 tarExtractHardLink(TarInfo *header, int extractFlag, int tostdoutFlag)
374 {
375         if (extractFlag==FALSE || tostdoutFlag==TRUE)
376                 return( TRUE);
377
378         if (link(header->linkname, header->name) < 0) {
379                 errorMsg("tar: %s: Cannot create hard link to '%s': %s\n", 
380                                 header->name, header->linkname, strerror(errno)); 
381                 return( FALSE);
382         }
383
384         /* Now set permissions etc for the new directory */
385         fixUpPermissions(header);
386         return( TRUE);
387 }
388
389 static int
390 tarExtractSymLink(TarInfo *header, int extractFlag, int tostdoutFlag)
391 {
392         if (extractFlag==FALSE || tostdoutFlag==TRUE)
393                 return( TRUE);
394
395 #ifdef  S_ISLNK
396         if (symlink(header->linkname, header->name) < 0) {
397                 errorMsg("tar: %s: Cannot create symlink to '%s': %s\n", 
398                                 header->name, header->linkname, strerror(errno)); 
399                 return( FALSE);
400         }
401         /* Try to change ownership of the symlink.
402          * If libs doesn't support that, don't bother.
403          * Changing the pointed-to-file is the Wrong Thing(tm).
404          */
405 #if (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 1)
406         lchown(header->name, header->uid, header->gid);
407 #endif
408
409         /* Do not change permissions or date on symlink,
410          * since it changes the pointed to file instead.  duh. */
411 #else
412         errorMsg("tar: %s: Cannot create symlink to '%s': %s\n", 
413                         header->name, header->linkname, 
414                         "symlinks not supported"); 
415 #endif
416         return( TRUE);
417 }
418
419 static int
420 tarExtractSpecial(TarInfo *header, int extractFlag, int tostdoutFlag)
421 {
422         if (extractFlag==FALSE || tostdoutFlag==TRUE)
423                 return( TRUE);
424
425         if (S_ISCHR(header->mode) || S_ISBLK(header->mode) || S_ISSOCK(header->mode)) {
426                 if (mknod(header->name, header->mode, makedev(header->devmajor, header->devminor)) < 0) {
427                         errorMsg("tar: %s: Cannot mknod: %s\n",
428                                 header->name, strerror(errno)); 
429                         return( FALSE);
430                 }
431         } else if (S_ISFIFO(header->mode)) {
432                 if (mkfifo(header->name, header->mode) < 0) {
433                         errorMsg("tar: %s: Cannot mkfifo: %s\n",
434                                 header->name, strerror(errno)); 
435                         return( FALSE);
436                 }
437         }
438
439         /* Now set permissions etc for the new directory */
440         fixUpPermissions(header);
441         return( TRUE);
442 }
443
444 /* Read an octal value in a field of the specified width, with optional
445  * spaces on both sides of the number and with an optional null character
446  * at the end.  Returns -1 on an illegal format.  */
447 static long getOctal(const char *cp, int size)
448 {
449         long val = 0;
450
451         for(;(size > 0) && (*cp == ' '); cp++, size--);
452         if ((size == 0) || !isOctal(*cp))
453                 return -1;
454         for(; (size > 0) && isOctal(*cp); size--) {
455                 val = val * 8 + *cp++ - '0';
456         }
457         for (;(size > 0) && (*cp == ' '); cp++, size--);
458         if ((size > 0) && *cp)
459                 return -1;
460         return val;
461 }
462
463
464 /* Parse the tar header and fill in the nice struct with the details */
465 static int
466 readTarHeader(struct TarHeader *rawHeader, struct TarInfo *header)
467 {
468         int i;
469         long chksum, sum=0;
470         unsigned char *s = (unsigned char *)rawHeader;
471
472         header->name  = rawHeader->name;
473         /* Check for and relativify any absolute paths */
474         if ( *(header->name) == '/' ) {
475                 static int alreadyWarned=FALSE;
476
477                 while (*(header->name) == '/')
478                         ++*(header->name);
479
480                 if (alreadyWarned == FALSE) {
481                         errorMsg("tar: Removing leading '/' from member names\n");
482                         alreadyWarned = TRUE;
483                 }
484         }
485
486         header->mode  = getOctal(rawHeader->mode, sizeof(rawHeader->mode));
487         header->uid   =  getOctal(rawHeader->uid, sizeof(rawHeader->uid));
488         header->gid   =  getOctal(rawHeader->gid, sizeof(rawHeader->gid));
489         header->size  = getOctal(rawHeader->size, sizeof(rawHeader->size));
490         header->mtime = getOctal(rawHeader->mtime, sizeof(rawHeader->mtime));
491         chksum = getOctal(rawHeader->chksum, sizeof(rawHeader->chksum));
492         header->type  = rawHeader->typeflag;
493         header->linkname  = rawHeader->linkname;
494         header->devmajor  = getOctal(rawHeader->devmajor, sizeof(rawHeader->devmajor));
495         header->devminor  = getOctal(rawHeader->devminor, sizeof(rawHeader->devminor));
496
497         /* Check the checksum */
498         for (i = sizeof(*rawHeader); i-- != 0;) {
499                 sum += *s++;
500         }
501         /* Remove the effects of the checksum field (replace 
502          * with blanks for the purposes of the checksum) */
503         s = rawHeader->chksum;
504         for (i = sizeof(rawHeader->chksum) ; i-- != 0;) {
505                 sum -= *s++;
506         }
507         sum += ' ' * sizeof(rawHeader->chksum);
508         if (sum == chksum )
509                 return ( TRUE);
510         return( FALSE);
511 }
512
513
514 /*
515  * Read a tar file and extract or list the specified files within it.
516  * If the list is empty than all files are extracted or listed.
517  */
518 static int readTarFile(const char* tarName, int extractFlag, int listFlag, 
519                 int tostdoutFlag, int verboseFlag, char** excludeList)
520 {
521         int status, tarFd=-1;
522         int errorFlag=FALSE;
523         TarHeader rawHeader;
524         TarInfo header;
525 #if defined BB_FEATURE_TAR_EXCLUDE
526         char** tmpList;
527 #endif
528
529         /* Open the tar file for reading.  */
530         if (!strcmp(tarName, "-"))
531                 tarFd = fileno(stdin);
532         else
533                 tarFd = open(tarName, O_RDONLY);
534         if (tarFd < 0) {
535                 errorMsg( "Error opening '%s': %s\n", tarName, strerror(errno));
536                 return ( FALSE);
537         }
538
539         /* Set the umask for this process so it doesn't 
540          * screw up permission setting for us later. */
541         umask(0);
542
543         /* Read the tar file, and iterate over it one file at a time */
544         while ( (status = fullRead(tarFd, (char*)&rawHeader, TAR_BLOCK_SIZE)) == TAR_BLOCK_SIZE ) {
545
546                 /* First, try to read the header */
547                 if ( readTarHeader(&rawHeader, &header) == FALSE ) {
548                         if ( *(header.name) == '\0' ) {
549                                 goto endgame;
550                         } else {
551                                 errorFlag=TRUE;
552                                 errorMsg("Bad tar header, skipping\n");
553                                 continue;
554                         }
555                 }
556                 if ( *(header.name) == '\0' )
557                                 goto endgame;
558                 header.tarFd = tarFd;
559
560 #if defined BB_FEATURE_TAR_EXCLUDE
561                 {
562                         int skipFlag=FALSE;
563                         /* Check for excluded files....  */
564                         for (tmpList=excludeList; tmpList && *tmpList; tmpList++) {
565                                 /* Do some extra hoop jumping for when directory names
566                                  * end in '/' but the entry in tmpList doesn't */
567                                 if (strncmp( *tmpList, header.name, strlen(*tmpList))==0 || (
568                                                         header.name[strlen(header.name)-1]=='/'
569                                                         && strncmp( *tmpList, header.name, 
570                                                                 MIN(strlen(header.name)-1, strlen(*tmpList)))==0)) {
571                                         /* If it is a regular file, pretend to extract it with
572                                          * the extractFlag set to FALSE, so the junk in the tarball
573                                          * is properly skipped over */
574                                         if ( header.type==REGTYPE || header.type==REGTYPE0 ) {
575                                                         tarExtractRegularFile(&header, FALSE, FALSE);
576                                         }
577                                         skipFlag=TRUE;
578                                         break;
579                                 }
580                         }
581                         /* There are not the droids you're looking for, move along */
582                         if (skipFlag==TRUE)
583                                 continue;
584                 }
585 #endif
586                 /* Special treatment if the list (-t) flag is on */
587                 if (verboseFlag == TRUE && extractFlag == FALSE) {
588                         int len, len1;
589                         char buf[35];
590                         struct tm *tm = localtime (&(header.mtime));
591
592                         len=printf("%s ", modeString(header.mode));
593                         memset(buf, 0, 8*sizeof(char));
594                         my_getpwuid(buf, header.uid);
595                         if (! *buf)
596                                 len+=printf("%d", header.uid);
597                         else
598                                 len+=printf("%s", buf);
599                         memset(buf, 0, 8*sizeof(char));
600                         my_getgrgid(buf, header.gid);
601                         if (! *buf)
602                                 len+=printf("/%-d ", header.gid);
603                         else
604                                 len+=printf("/%-s ", buf);
605
606                         if (header.type==CHRTYPE || header.type==BLKTYPE) {
607                                 len1=snprintf(buf, sizeof(buf), "%ld,%-ld ", 
608                                                 header.devmajor, header.devminor);
609                         } else {
610                                 len1=snprintf(buf, sizeof(buf), "%lu ", (long)header.size);
611                         }
612                         /* Jump through some hoops to make the columns match up */
613                         for(;(len+len1)<31;len++)
614                                 printf(" ");
615                         printf(buf);
616
617                         /* Use ISO 8610 time format */
618                         if (tm) { 
619                                 printf ("%04d-%02d-%02d %02d:%02d:%02d ", 
620                                                 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, 
621                                                 tm->tm_hour, tm->tm_min, tm->tm_sec);
622                         }
623                 }
624                 /* List contents if we are supposed to do that */
625                 if (verboseFlag == TRUE || listFlag == TRUE) {
626                         /* Now the normal listing */
627                         printf("%s", header.name);
628                 }
629                 if (verboseFlag == TRUE && listFlag == TRUE) {
630                         /* If this is a link, say so */
631                         if (header.type==LNKTYPE)
632                                 printf(" link to %s", header.linkname);
633                         else if (header.type==SYMTYPE)
634                                 printf(" -> %s", header.linkname);
635                 }
636                 if (verboseFlag == TRUE || listFlag == TRUE) {
637                         printf("\n");
638                 }
639
640                 /* Remove any clutter lying in our way */
641                 unlink( header.name);
642
643                 /* If we got here, we can be certain we have a legitimate 
644                  * header to work with.  So work with it.  */
645                 switch ( header.type ) {
646                         case REGTYPE:
647                         case REGTYPE0:
648                                 /* If the name ends in a '/' then assume it is
649                                  * supposed to be a directory, and fall through */
650                                 if (header.name[strlen(header.name)-1] != '/') {
651                                         if (tarExtractRegularFile(&header, extractFlag, tostdoutFlag)==FALSE)
652                                                 errorFlag=TRUE;
653                                         break;
654                                 }
655                         case DIRTYPE:
656                                 if (tarExtractDirectory( &header, extractFlag, tostdoutFlag)==FALSE)
657                                         errorFlag=TRUE;
658                                 break;
659                         case LNKTYPE:
660                                 if (tarExtractHardLink( &header, extractFlag, tostdoutFlag)==FALSE)
661                                         errorFlag=TRUE;
662                                 break;
663                         case SYMTYPE:
664                                 if (tarExtractSymLink( &header, extractFlag, tostdoutFlag)==FALSE)
665                                         errorFlag=TRUE;
666                                 break;
667                         case CHRTYPE:
668                         case BLKTYPE:
669                         case FIFOTYPE:
670                                 if (tarExtractSpecial( &header, extractFlag, tostdoutFlag)==FALSE)
671                                         errorFlag=TRUE;
672                                 break;
673                         default:
674                                 close( tarFd);
675                                 return( FALSE);
676                 }
677         }
678         close(tarFd);
679         if (status > 0) {
680                 /* Bummer - we read a partial header */
681                 errorMsg( "Error reading '%s': %s\n", tarName, strerror(errno));
682                 return ( FALSE);
683         }
684         else if (errorFlag==TRUE) {
685                 errorMsg( "tar: Error exit delayed from previous errors\n");
686                 return( FALSE);
687         } else 
688                 return( status);
689
690         /* Stuff to do when we are done */
691 endgame:
692         close( tarFd);
693         if ( *(header.name) == '\0' ) {
694                 if (errorFlag==TRUE)
695                         errorMsg( "tar: Error exit delayed from previous errors\n");
696                 else
697                         return( TRUE);
698         } 
699         return( FALSE);
700 }
701
702
703 #ifdef BB_FEATURE_TAR_CREATE
704
705 /* Some info to be carried along when creating a new tarball */
706 struct TarBallInfo
707 {
708         char* fileName;               /* File name of the tarball */
709         int tarFd;                    /* Open-for-write file descriptor
710                                                                          for the tarball */
711         struct stat statBuf;          /* Stat info for the tarball, letting
712                                                                          us know the inode and device that the
713                                                                          tarball lives, so we can avoid trying 
714                                                                          to include the tarball into itself */
715         int verboseFlag;              /* Whether to print extra stuff or not */
716         char** excludeList;           /* List of files to not include */
717 };
718 typedef struct TarBallInfo TarBallInfo;
719
720
721 /* Put an octal string into the specified buffer.
722  * The number is zero and space padded and possibly null padded.
723  * Returns TRUE if successful.  */ 
724 static int putOctal (char *cp, int len, long value)
725 {
726         int tempLength;
727         char tempBuffer[32];
728         char *tempString = tempBuffer;
729
730         /* Create a string of the specified length with an initial space,
731          * leading zeroes and the octal number, and a trailing null.  */
732         sprintf (tempString, "%0*lo", len - 1, value);
733
734         /* If the string is too large, suppress the leading space.  */
735         tempLength = strlen (tempString) + 1;
736         if (tempLength > len) {
737                 tempLength--;
738                 tempString++;
739         }
740
741         /* If the string is still too large, suppress the trailing null.  */
742         if (tempLength > len)
743                 tempLength--;
744
745         /* If the string is still too large, fail.  */
746         if (tempLength > len)
747                 return FALSE;
748
749         /* Copy the string to the field.  */
750         memcpy (cp, tempString, len);
751
752         return TRUE;
753 }
754
755 /* Write out a tar header for the specified file/directory/whatever */
756 static int
757 writeTarHeader(struct TarBallInfo *tbInfo, const char *fileName, struct stat *statbuf)
758 {
759         long chksum=0;
760         struct TarHeader header;
761 #if defined BB_FEATURE_TAR_EXCLUDE
762         char** tmpList;
763 #endif
764         const unsigned char *cp = (const unsigned char *) &header;
765         ssize_t size = sizeof(struct TarHeader);
766
767         memset( &header, 0, size);
768
769         if (*fileName=='/') {
770                 static int alreadyWarned=FALSE;
771                 if (alreadyWarned==FALSE) {
772                         errorMsg("tar: Removing leading '/' from member names\n");
773                         alreadyWarned=TRUE;
774                 }
775                 strncpy(header.name, fileName+1, sizeof(header.name)); 
776         }
777         else {
778                 strncpy(header.name, fileName, sizeof(header.name)); 
779         }
780
781 #if defined BB_FEATURE_TAR_EXCLUDE
782         /* Check for excluded files....  */
783         for (tmpList=tbInfo->excludeList; tmpList && *tmpList; tmpList++) {
784                 /* Do some extra hoop jumping for when directory names
785                  * end in '/' but the entry in tmpList doesn't */
786                 if (strncmp( *tmpList, header.name, strlen(*tmpList))==0 || (
787                                         header.name[strlen(header.name)-1]=='/'
788                                         && strncmp( *tmpList, header.name, 
789                                                 MIN(strlen(header.name)-1, strlen(*tmpList)))==0)) {
790                         /* Set the mode to something that is not a regular file, thereby
791                          * faking out writeTarFile into thinking that nothing further need
792                          * be done for this file.  Yes, I know this is ugly, but it works. */
793                         statbuf->st_mode = 0;
794                         return( TRUE);
795                 }
796         }
797 #endif
798
799         putOctal(header.mode, sizeof(header.mode), statbuf->st_mode);
800         putOctal(header.uid, sizeof(header.uid), statbuf->st_uid);
801         putOctal(header.gid, sizeof(header.gid), statbuf->st_gid);
802         putOctal(header.size, sizeof(header.size), 0); /* Regular file size is handled later */
803         putOctal(header.mtime, sizeof(header.mtime), statbuf->st_mtime);
804         strncpy(header.magic, TAR_MAGIC TAR_VERSION, 
805                         TAR_MAGIC_LEN + TAR_VERSION_LEN );
806
807         /* Enter the user and group names (default to root if it fails) */
808         my_getpwuid(header.uname, statbuf->st_uid);
809         if (! *header.uname)
810                 strcpy(header.uname, "root");
811         my_getgrgid(header.gname, statbuf->st_gid);
812         if (! *header.uname)
813                 strcpy(header.uname, "root");
814
815         /* WARNING/NOTICE: I break Hard Links */
816         if (S_ISLNK(statbuf->st_mode)) {
817                 char buffer[BUFSIZ];
818                 header.typeflag  = SYMTYPE;
819                 if ( readlink(fileName, buffer, sizeof(buffer) - 1) < 0) {
820                         errorMsg("Error reading symlink '%s': %s\n", header.name, strerror(errno));
821                         return ( FALSE);
822                 }
823                 strncpy(header.linkname, buffer, sizeof(header.linkname)); 
824         } else if (S_ISDIR(statbuf->st_mode)) {
825                 header.typeflag  = DIRTYPE;
826                 strncat(header.name, "/", sizeof(header.name)); 
827         } else if (S_ISCHR(statbuf->st_mode)) {
828                 header.typeflag  = CHRTYPE;
829                 putOctal(header.devmajor, sizeof(header.devmajor), MAJOR(statbuf->st_rdev));
830                 putOctal(header.devminor, sizeof(header.devminor), MINOR(statbuf->st_rdev));
831         } else if (S_ISBLK(statbuf->st_mode)) {
832                 header.typeflag  = BLKTYPE;
833                 putOctal(header.devmajor, sizeof(header.devmajor), MAJOR(statbuf->st_rdev));
834                 putOctal(header.devminor, sizeof(header.devminor), MINOR(statbuf->st_rdev));
835         } else if (S_ISFIFO(statbuf->st_mode)) {
836                 header.typeflag  = FIFOTYPE;
837         } else if (S_ISREG(statbuf->st_mode)) {
838                 header.typeflag  = REGTYPE;
839                 putOctal(header.size, sizeof(header.size), statbuf->st_size);
840         } else {
841                 errorMsg("tar: %s: Unknown file type\n", fileName);
842                 return ( FALSE);
843         }
844
845         /* Calculate and store the checksum (i.e. the sum of all of the bytes of
846          * the header).  The checksum field must be filled with blanks for the
847          * calculation.  The checksum field is formatted differently from the
848          * other fields: it has [6] digits, a null, then a space -- rather than
849          * digits, followed by a null like the other fields... */
850         memset(header.chksum, ' ', sizeof(header.chksum));
851         cp = (const unsigned char *) &header;
852         while (size-- > 0)
853                 chksum += *cp++;
854         putOctal(header.chksum, 7, chksum);
855         
856         /* Now write the header out to disk */
857         if ((size=fullWrite(tbInfo->tarFd, (char*)&header, sizeof(struct TarHeader))) < 0) {
858                 errorMsg(io_error, fileName, strerror(errno)); 
859                 return ( FALSE);
860         }
861         /* Pad the header up to the tar block size */
862         for (; size<TAR_BLOCK_SIZE; size++) {
863                 write(tbInfo->tarFd, "\0", 1);
864         }
865         /* Now do the verbose thing (or not) */
866         if (tbInfo->verboseFlag==TRUE)
867                 fprintf(stdout, "%s\n", header.name);
868
869         return ( TRUE);
870 }
871
872
873 static int writeFileToTarball(const char *fileName, struct stat *statbuf, void* userData)
874 {
875         struct TarBallInfo *tbInfo = (struct TarBallInfo *)userData;
876
877         /* It is against the rules to archive a socket */
878         if (S_ISSOCK(statbuf->st_mode)) {
879                 errorMsg("tar: %s: socket ignored\n", fileName);
880                 return( TRUE);
881         }
882
883         /* It is a bad idea to store the archive we are in the process of creating,
884          * so check the device and inode to be sure that this particular file isn't
885          * the new tarball */
886         if (tbInfo->statBuf.st_dev == statbuf->st_dev &&
887                         tbInfo->statBuf.st_ino == statbuf->st_ino) {
888                 errorMsg("tar: %s: file is the archive; skipping\n", fileName);
889                 return( TRUE);
890         }
891
892         if (writeTarHeader(tbInfo, fileName, statbuf)==FALSE) {
893                 return( FALSE);
894         } 
895
896         /* Now, if the file is a regular file, copy it out to the tarball */
897         if (S_ISREG(statbuf->st_mode)) {
898                 int  inputFileFd;
899                 char buffer[BUFSIZ];
900                 ssize_t size=0, readSize=0;
901
902                 /* open the file we want to archive, and make sure all is well */
903                 if ((inputFileFd = open(fileName, O_RDONLY)) < 0) {
904                         errorMsg("tar: %s: Cannot open: %s\n", fileName, strerror(errno));
905                         return( FALSE);
906                 }
907                 
908                 /* write the file to the archive */
909                 while ( (size = fullRead(inputFileFd, buffer, sizeof(buffer))) > 0 ) {
910                         if (fullWrite(tbInfo->tarFd, buffer, size) != size ) {
911                                 /* Output file seems to have a problem */
912                                 errorMsg(io_error, fileName, strerror(errno)); 
913                                 return( FALSE);
914                         }
915                         readSize+=size;
916                 }
917                 if (size == -1) {
918                         errorMsg(io_error, fileName, strerror(errno)); 
919                         return( FALSE);
920                 }
921                 /* Pad the file up to the tar block size */
922                 for (; (readSize%TAR_BLOCK_SIZE) != 0; readSize++) {
923                         write(tbInfo->tarFd, "\0", 1);
924                 }
925                 close( inputFileFd);
926         }
927
928         return( TRUE);
929 }
930
931 static int writeTarFile(const char* tarName, int tostdoutFlag, 
932                 int verboseFlag, int argc, char **argv, char** excludeList)
933 {
934         int tarFd=-1;
935         int errorFlag=FALSE;
936         ssize_t size;
937         struct TarBallInfo tbInfo;
938         tbInfo.verboseFlag = verboseFlag;
939
940         /* Make sure there is at least one file to tar up.  */
941         if (argc <= 0)
942                 fatalError("tar: Cowardly refusing to create an empty archive\n");
943
944         /* Open the tar file for writing.  */
945         if (tostdoutFlag == TRUE)
946                 tbInfo.tarFd = fileno(stdout);
947         else
948                 tbInfo.tarFd = open (tarName, O_WRONLY | O_CREAT | O_TRUNC, 0644);
949         if (tbInfo.tarFd < 0) {
950                 errorMsg( "tar: Error opening '%s': %s\n", tarName, strerror(errno));
951                 return ( FALSE);
952         }
953         tbInfo.excludeList=excludeList;
954         /* Store the stat info for the tarball's file, so
955          * can avoid including the tarball into itself....  */
956         if (fstat(tbInfo.tarFd, &tbInfo.statBuf) < 0)
957                 fatalError(io_error, tarName, strerror(errno)); 
958
959         /* Set the umask for this process so it doesn't 
960          * screw up permission setting for us later. */
961         umask(0);
962
963         /* Read the directory/files and iterate over them one at a time */
964         while (argc-- > 0) {
965                 if (recursiveAction(*argv++, TRUE, FALSE, FALSE,
966                                         writeFileToTarball, writeFileToTarball, 
967                                         (void*) &tbInfo) == FALSE) {
968                         errorFlag = TRUE;
969                 }
970         }
971         /* Write two empty blocks to the end of the archive */
972         for (size=0; size<(2*TAR_BLOCK_SIZE); size++) {
973                 write(tbInfo.tarFd, "\0", 1);
974         }
975
976         /* To be pedantically correct, we would check if the tarball
977          * is smaller then 20 tar blocks, and pad it if it was smaller,
978          * but that isn't necessary for GNU tar interoperability, and
979          * so is considered a waste of space */
980
981         /* Hang up the tools, close up shop, head home */
982         close(tarFd);
983         if (errorFlag == TRUE) {
984                 errorMsg("tar: Error exit delayed from previous errors\n");
985                 return(FALSE);
986         }
987         return( TRUE);
988 }
989
990
991 #endif
992