Update to ar.c from Glenn McGrath. His comments follow:
[oweals/busybox.git] / archival / ar.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini ar implementation for busybox 
4  *
5  * Copyright (C) 2000 by Glenn McGrath
6  * Written by Glenn McGrath <bug1@netconnect.com.au> 1 June 2000
7  *
8  * Based in part on BusyBox tar, Debian dpkg-deb and GNU ar.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  * General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23  *
24  * Last modified 10 June 2000
25  */
26
27
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <errno.h>
31 #include <ctype.h>
32 #include <time.h>
33 #include <utime.h>
34 #include <sys/types.h>
35 #include "internal.h"
36
37 #define AR_BLOCK_SIZE 60
38 #define AR_PRESERVE_DATE 1
39 #define AR_VERBOSE       2
40 #define AR_DISPLAY       4
41 #define AR_EXT_TO_FILE   8
42 #define AR_EXT_TO_STDOUT 16
43
44 #define BB_DECLARE_EXTERN
45 #define bb_need_io_error
46 #include "messages.c"
47
48 struct ArHeader {                               /* Byte Offset */
49         char ar_name[16];                       /*  0-15 */
50         char ar_date[12];                       /* 16-27 */
51         char ar_uid[6], ar_gid[6];      /* 28-39 */
52         char ar_mode[8];                        /* 40-47 */
53         char ar_size[10];                       /* 48-57 */
54         char ar_fmag[2];                        /* 58-59 */
55 };
56 typedef struct ArHeader ArHeader;
57
58 struct ArInfo {
59         char name[17];                          /* File name */
60         time_t date;                            /* long int, No of seconds since epoch */
61         uid_t uid;                                      /* unsigned int, Numeric UID */
62         gid_t gid;                                      /* unsigned int, Numeric GID */
63         mode_t mode;                            /* unsigned int, Unix mode */
64         size_t size;                            /* int, Size of the file */
65 };
66 typedef struct ArInfo ArInfo;
67
68 static const char ar_usage[] = "ar [optxvV] archive [filenames] \n"
69 #ifndef BB_FEATURE_TRIVIAL_HELP
70         "\nExtract or list files from an ar archive.\n\n"
71         "Options:\n"
72         "\to\t\tpreserve original dates\n"
73         "\tp\t\textract to stdout\n"
74         "\tt\t\tlist\n"
75         "\tx\t\textract\n"
76         "\tv\t\tverbosely list files processed\n"
77 #endif
78         ;
79
80 /*
81  * Display details of a file, verbosly if funct=2   
82  */
83 static void displayEntry(struct ArInfo *entry, int funct)
84 {
85         /* TODO convert mode to string */
86         if ((funct & AR_VERBOSE) == AR_VERBOSE)
87                 printf("%i %i/%i %8i %s ", entry->mode, entry->uid, entry->gid,
88                            entry->size, timeString(entry->date));
89         printf("%s\n", entry->name);
90 }
91
92 /* this is from tar.c remove later*/
93 static long getOctal(const char *cp, int size)
94 {
95         long val = 0;
96
97         for(;(size > 0) && (*cp == ' '); cp++, size--);
98         if ((size == 0) || !isOctal(*cp))
99                 return -1;
100         for(; (size > 0) && isOctal(*cp); size--) {
101                 val = val * 8 + *cp++ - '0';
102         }
103         for (;(size > 0) && (*cp == ' '); cp++, size--);
104         if ((size > 0) && *cp)
105                 return -1;
106         return val;
107 }
108
109 /*
110  * Converts from the char based struct to a new struct with stricter types
111  */
112 static int processArHeader(struct ArHeader *rawHeader, struct ArInfo *header)
113 {
114         int count2;
115         int count;
116         
117         /* check end of header marker is valid */
118         if ((rawHeader->ar_fmag[0]!='`') || (rawHeader->ar_fmag[1]!='\n')) 
119                 return(FALSE); 
120
121         /* convert filename */ 
122         for (count = 0; count < 16; count++) {
123                 /* allow spaces in filename except at the end */
124                 if (rawHeader->ar_name[count] == ' ') {
125                         for (count2 = count; count2 < 16; count2++)
126                                 if (!isspace(rawHeader->ar_name[count2]))
127                                         break;
128                         if (count2 >= 16)
129                                 break;
130                 }
131                 /* GNU ar uses '/' as an end of filename marker */
132                 if (rawHeader->ar_name[count] == '/')
133                         break;
134                 header->name[count] = rawHeader->ar_name[count];
135         }
136         header->name[count] = '\0';
137         header->date = atoi(rawHeader->ar_date);
138         header->uid = atoi(rawHeader->ar_uid);
139         header->gid = atoi(rawHeader->ar_gid);
140         header->mode = getOctal(rawHeader->ar_mode, sizeof(rawHeader->ar_mode));
141         header->size = atoi(rawHeader->ar_size);
142         return (TRUE);
143 }
144
145 /*
146  * Copy size bytes from current position if srcFd to current position in dstFd
147  * taken from tarExtractRegularFile in tar.c, remove later
148  */
149 static int copySubFile(int srcFd, int dstFd, int copySize)
150 {
151         int readSize, writeSize, doneSize;
152         char buffer[BUFSIZ];
153
154         while (copySize > 0) {
155                 if (copySize > BUFSIZ)
156                         readSize = BUFSIZ;
157                 else
158                         readSize = copySize;
159                 writeSize = fullRead(srcFd, buffer, readSize);
160                 if (writeSize <= 0) {
161                         errorMsg(io_error, "copySubFile :", strerror(errno));
162                         return (FALSE);
163                 }
164                 doneSize = fullWrite(dstFd, buffer, writeSize);
165                 if (doneSize <= 0) {
166                         errorMsg(io_error, "copySubFile :", strerror(errno));
167                         return (FALSE);
168                 }
169                 copySize -= doneSize;
170         }
171         return (TRUE);
172 }
173
174 /*
175  * Extract the file described in ArInfo to the specified path 
176  * set the new files uid, gid and mode 
177  */
178 static int extractToFile(struct ArInfo *file, int funct, int srcFd, const char *path)
179 {
180         int dstFd, temp;
181         struct stat tmpStat;
182         char *pathname = NULL;
183         struct utimbuf newtime;
184         
185         if ((temp = isDirectory(path, TRUE, &tmpStat)) != TRUE) {
186                 if (!createPath(path, 0777)) {
187                         fatalError("Cannot extract to specified path");
188                         return (FALSE);
189                 }
190         }
191         temp = (strlen(path) + 16);
192         pathname = (char *) xmalloc(temp);
193         pathname = strcpy(pathname, path);
194         pathname = strcat(pathname, file->name);
195         dstFd = device_open(pathname, O_WRONLY | O_CREAT);
196         temp = copySubFile(srcFd, dstFd, file->size);
197         fchown(dstFd, file->uid, file->gid);
198         fchmod(dstFd, file->mode);
199         close(dstFd);
200         if ((funct&AR_PRESERVE_DATE)==AR_PRESERVE_DATE) 
201                 newtime.modtime=file->date;
202         else
203                 newtime.modtime=time(0);
204         newtime.actime=time(0);
205         temp = utime(pathname, &newtime);
206         return (TRUE);
207 }
208
209 /*
210  * Return a file descriptor for the specified file and do error checks
211  */
212 static int getArFd(char *filename)
213 {
214         int arFd;
215         char arVersion[8];
216
217         arFd = open(filename, O_RDONLY);
218         if (arFd < 0) { 
219                 errorMsg("Error opening '%s': %s\n", filename, strerror(errno));
220                 return (FALSE);
221         }
222         if (fullRead(arFd, arVersion, 8) <= 0) {
223                 errorMsg( "ar: Unexpected EOF in archive\n");
224                 return (FALSE);
225         }
226         if (strncmp(arVersion,"!<arch>",7) != 0) {
227                 errorMsg("ar header fails check ");
228                 return(FALSE);
229         }
230         return arFd;
231 }
232
233 /*
234  * Step through the ar file and process it one entry at a time
235  * fileList[0] is the name of the ar archive
236  * fileList[1] and up are filenames to extract from the archive
237  * funct contains flags to specify the actions to be performed 
238  */
239 static int readArFile(char *fileList[16], int fileListSize, int funct)
240 {
241         int arFd, status, extFileFlag, i, lastOffset=0;
242         ArHeader rawArHeader;
243         ArInfo arEntry;
244
245         /* open the ar archive */
246         arFd=getArFd(fileList[0]);
247
248         /* read the first header, then loop until ono more headers */ 
249         while ((status = fullRead(arFd, (char *) &rawArHeader, AR_BLOCK_SIZE))
250                    == AR_BLOCK_SIZE) {
251
252                 /* check the header is valid, if not try reading the header
253                    agian with an offset of 1, needed as some ar archive end
254                    with a '\n' which isnt counted in specified file size */
255                 if ((status=processArHeader(&rawArHeader, &arEntry))==FALSE ) {
256                         if ((i=lseek(arFd, 0, SEEK_CUR))==(lastOffset+60)) 
257                                 lseek(arFd, lastOffset+1, SEEK_SET);
258                         else 
259                                 return(FALSE);
260                 }
261                 else {  
262                         extFileFlag=0;
263                         
264                         if (funct&AR_DISPLAY) 
265                                 displayEntry(&arEntry, funct);
266
267                         /* check file was specified to be extracted only if 
268                            some file were specified */
269                         if ((funct&AR_EXT_TO_FILE) || (funct&AR_EXT_TO_STDOUT)){
270                                 if (fileListSize==1)
271                                         extFileFlag=1;
272                                 else {
273                                         for( i=1; i<=fileListSize; i++)
274                                                 if ((status=(strcmp(fileList[i],arEntry.name)))==0)
275                                                         extFileFlag=1;
276                                 }
277                         }
278                         if (extFileFlag==1) { 
279                                 if (funct&AR_EXT_TO_FILE)
280                                         extractToFile(&arEntry, funct, arFd, "./");
281                                 else    
282                                         copySubFile(arFd,fileno(stdout),arEntry.size);
283                         }
284                         else
285                                 lseek(arFd, arEntry.size, SEEK_CUR);
286                         lastOffset=lseek(arFd, 0, SEEK_CUR);
287                 } /* if processArHeader */
288         }  /* while */
289         return (TRUE);
290 }
291
292 extern int ar_main(int argc, char **argv)
293 {
294         int funct = 0, ret=0, i=0;
295         char *fileList[16], c, *opt_ptr;
296
297         if (argc < 2)
298                 usage(ar_usage);
299
300         opt_ptr = argv[1];
301         if (*opt_ptr == '-')
302                 ++opt_ptr;
303         while ((c = *opt_ptr++) != '\0') {
304                 switch (c) {
305                 case 'o':                               /* preserver original dates */
306                         funct = funct | 1;
307                         break;
308                 case 'p':                               /* extract to stdout */
309                         funct = funct | 16;
310                         break;
311                 case 't':                               /* display contents */
312                         funct = funct | 4;
313                         break;
314                 case 'x':                               /* extract contents of archive */
315                         funct = funct | 8;
316                         break;
317                 case 'v':                               /* be verbose */
318                         funct = funct | 2;
319                         break;
320                 default:
321                         usage(ar_usage);
322                 }
323         }
324
325         for(i=0; i<(argc-2); i++) 
326                 fileList[i]=argv[i+2];
327         
328         if (funct > 3)
329                 ret = readArFile(fileList, (argc-2), funct);
330         
331         return (ret);
332 }