Another update from Larry:
[oweals/busybox.git] / libbb / untar.c
1 /*
2  *  This program is free software; you can redistribute it and/or modify
3  *  it under the terms of the GNU General Public License as published by
4  *  the Free Software Foundation; either version 2 of the License, or
5  *  (at your option) any later version.
6  *
7  *  This program is distributed in the hope that it will be useful,
8  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *  GNU Library General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15  *
16  * NOTE: This is used by deb_extract to avoid calling the whole tar applet.
17  * Its functionality should be merged with tar to avoid duplication
18  */
19
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include "libbb.h"
24
25 /*
26  * stdout can be redircted to FILE *output
27  * If const char *file_prefix isnt NULL is prepended to all the extracted filenames.
28  * The purpose of const char *argument depends on the value of const int untar_function
29  */
30
31 extern char *untar(FILE *src_tar_file, FILE *output, const int untar_function,
32         const char *argument, const char *file_prefix)
33 {
34         typedef struct raw_tar_header {
35         char name[100];               /*   0-99 */
36         char mode[8];                 /* 100-107 */
37         char uid[8];                  /* 108-115 */
38         char gid[8];                  /* 116-123 */
39         char size[12];                /* 124-135 */
40         char mtime[12];               /* 136-147 */
41         char chksum[8];               /* 148-155 */
42         char typeflag;                /* 156-156 */
43         char linkname[100];           /* 157-256 */
44         char magic[6];                /* 257-262 */
45         char version[2];              /* 263-264 */
46         char uname[32];               /* 265-296 */
47         char gname[32];               /* 297-328 */
48         char devmajor[8];             /* 329-336 */
49         char devminor[8];             /* 337-344 */
50         char prefix[155];             /* 345-499 */
51         char padding[12];             /* 500-512 */
52         } raw_tar_header_t;
53
54         raw_tar_header_t raw_tar_header;
55         unsigned char *temp = (unsigned char *) &raw_tar_header;
56         long i;
57         long next_header_offset = 0;
58         long uncompressed_count = 0;
59         size_t size;
60         mode_t mode;
61
62         while (fread((char *) &raw_tar_header, 1, 512, src_tar_file) == 512) {
63                 long sum = 0;
64                 
65                 if (ferror(src_tar_file) || feof(src_tar_file)) {
66                         perror_msg("untar: ");
67                         break;
68                 }
69
70                 uncompressed_count += 512;
71
72                 /* Check header has valid magic */
73                 if (strncmp(raw_tar_header.magic, "ustar", 5) != 0) {
74 /*
75  * FIXME, Need HELP with this
76  *
77  * This has to fail silently or it incorrectly reports errors when called from
78  * deb_extract.
79  * The problem is deb_extract decompresses the .gz file in a child process and
80  * untar reads from the child proccess. The child process finishes and exits,
81  * but fread reads 0's from the src_tar_file even though the child
82  * closed its handle.
83  */
84 //                      error_msg("Invalid tar magic");
85                         break;
86                 }
87
88                 /* Do checksum on headers */
89         for (i =  0; i < 148 ; i++) {
90                         sum += temp[i];
91                 }
92         sum += ' ' * 8;
93                 for (i =  156; i < 512 ; i++) {
94                         sum += temp[i];
95                 }
96         if (sum != strtol(raw_tar_header.chksum, NULL, 8)) {
97                         error_msg("Invalid tar header checksum");
98                         break;
99                 }
100
101                 /* convert to type'ed variables */
102         size = strtol(raw_tar_header.size, NULL, 8);
103                 parse_mode(raw_tar_header.mode, &mode);
104
105                 /* start of next header is at */
106                 next_header_offset = uncompressed_count + size;
107                 if (size % 512 != 0) {
108                         next_header_offset += (512 - size % 512);
109                 }
110
111                 /* If an exclude list is specified check current file against list */
112 #if 0
113                 if (*exclude_list != NULL) {
114                         i = 0;
115                         while (exclude_list[i] != 0) {
116                                 if (strncmp(exclude_list[i], raw_tar_header.name, strlen(raw_tar_header.name)) == 0) {
117                                         break;
118                                 }
119                                 i++;
120                         }
121                 if (exclude_list[i] != 0) {
122                                 continue;
123                         }
124                 }
125 #endif
126                 if (untar_function & (extract_contents | extract_verbose_extract | extract_contents_to_file)) {
127                         fprintf(output, "%s\n", raw_tar_header.name);
128                 }
129
130                 switch (raw_tar_header.typeflag ) {
131                         case '0':
132                         case '\0': {
133                                 /* If the name ends in a '/' then assume it is
134                                  * supposed to be a directory, and fall through
135                                  */
136                                 int name_length = strlen(raw_tar_header.name);
137                                 if (raw_tar_header.name[name_length - 1] != '/') {
138                                         switch (untar_function) {
139                                                 case (extract_extract):
140                                                 case (extract_verbose_extract):
141                                                 case (extract_control): {
142                                                                 FILE *dst_file = NULL;
143                                                                 char *full_name;
144
145                                                                 if (file_prefix != NULL) {
146                                                                         full_name = xmalloc(strlen(argument) + strlen(file_prefix) + name_length + 3);
147                                                                         sprintf(full_name, "%s/%s.%s", argument, file_prefix, strrchr(raw_tar_header.name, '/') + 1);
148                                                                 } else {
149                                                                         full_name = concat_path_file(argument, raw_tar_header.name);
150                                                                 }
151                                                                 dst_file = wfopen(full_name, "w");
152                                                                 copy_file_chunk(src_tar_file, dst_file, (unsigned long long) size);
153                                                                 uncompressed_count += size;
154                                                                 fclose(dst_file);
155                                                                 chmod(full_name, mode);
156                                                                 free(full_name);
157                                                         }
158                                                         break;
159                                                 case (extract_info):
160                                                         if (strstr(raw_tar_header.name, argument) != NULL) {
161                                                                 copy_file_chunk(src_tar_file, stdout, (unsigned long long) size);
162                                                                 uncompressed_count += size;
163                                                         }
164                                                         break;
165                                                 case (extract_field):
166                                                         if (strstr(raw_tar_header.name, "./control") != NULL) {
167                                                                 return(read_text_file_to_buffer(src_tar_file));
168                                                         }
169                                                         break;
170                                         }
171                                         break;
172                                 }
173                         }
174                         case '5':
175                                 if (untar_function & (extract_extract | extract_verbose_extract | extract_control)) {
176                                         int ret;
177                                         char *dir;
178                                         dir = concat_path_file(argument, raw_tar_header.name);
179                                         ret = create_path(dir, mode);
180                                         free(dir);
181                                         if (ret == FALSE) {
182                                                 perror_msg("%s: Cannot mkdir", raw_tar_header.name); 
183                                                 return NULL;
184                                         }
185                                         chmod(dir, mode);
186                                 }
187                                 break;
188                         case '1':
189                                 if (untar_function & (extract_extract | extract_verbose_extract | extract_control)) {
190                                         if (link(raw_tar_header.linkname, raw_tar_header.name) < 0) {
191                                                 perror_msg("%s: Cannot create hard link to '%s'", raw_tar_header.name, raw_tar_header.linkname); 
192                                                 return NULL;
193                                         }
194                                 }
195                                 break;
196                         case '2':
197                                 if (untar_function & (extract_extract | extract_verbose_extract | extract_control)) {
198                                         if (symlink(raw_tar_header.linkname, raw_tar_header.name) < 0) {
199                                                 perror_msg("%s: Cannot create symlink to '%s'", raw_tar_header.name, raw_tar_header.linkname); 
200                                                 return NULL;
201                                         }
202                                 }
203                                 break;
204                         case '3':
205                         case '4':
206                         case '6':
207 //                              if (tarExtractSpecial( &header, extractFlag, tostdoutFlag)==FALSE)
208 //                                      errorFlag=TRUE;
209 //                              break;
210                         default:
211                                 error_msg("Unknown file type '%c' in tar file", raw_tar_header.typeflag);
212                                 return NULL;
213                 }
214                 /*
215                  * Seek to start of next block, cant use fseek as unzip() does support it
216                  */
217                 while (uncompressed_count < next_header_offset) {
218                         if (fgetc(src_tar_file) == EOF) {
219                                 break;
220                         }
221                         uncompressed_count++;
222                 }
223         }
224         return NULL;
225 }