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