Patch from Laurence Anderson <L.D.Anderson@warwick.ac.uk> for
[oweals/busybox.git] / archival / libunarchive / unarchive.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
17 #include <errno.h>
18 #include <fnmatch.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <utime.h>
24 #include <sys/wait.h>
25 #include <signal.h>
26 #include "unarchive.h"
27 #include "busybox.h"
28
29 /* Extract the data postioned at src_stream to either filesystem, stdout or 
30  * buffer depending on the value of 'function' which is defined in libbb.h 
31  *
32  * prefix doesnt have to be just a directory, it may prefix the filename as well.
33  *
34  * e.g. '/var/lib/dpkg/info/dpkg.' will extract all files to the base bath 
35  * '/var/lib/dpkg/info/' and all files/dirs created in that dir will have 
36  * 'dpkg.' as their prefix
37  *
38  * For this reason if prefix does point to a dir then it must end with a
39  * trailing '/' or else the last dir will be assumed to be the file prefix 
40  */
41 char *extract_archive(FILE *src_stream, FILE *out_stream, const file_header_t *file_entry,
42  const int function, const char *prefix)
43 {
44         FILE *dst_stream = NULL;
45         char *full_name = NULL;
46         char *buffer = NULL;
47         struct utimbuf t;
48
49         /* prefix doesnt have to be a proper path it may prepend 
50          * the filename as well */
51         if (prefix != NULL) {
52                 /* strip leading '/' in filename to extract as prefix may not be dir */
53                 /* Cant use concat_path_file here as prefix might not be a directory */
54                 char *path = file_entry->name;
55                 if (strncmp("./", path, 2) == 0) {
56                         path += 2;
57                         if (strlen(path) == 0) {
58                                 return(NULL);
59                         }
60                 }
61                 full_name = xmalloc(strlen(prefix) + strlen(path) + 1);
62                 strcpy(full_name, prefix);
63                 strcat(full_name, path);
64         } else {
65                 full_name = file_entry->name;
66         }
67         if (function & extract_to_stdout) {
68                 if (S_ISREG(file_entry->mode)) {
69                         copy_file_chunk(src_stream, out_stream, file_entry->size);                      
70                         archive_offset += file_entry->size;
71                 }
72         }
73         else if (function & extract_one_to_buffer) { 
74                 if (S_ISREG(file_entry->mode)) {
75                         buffer = (char *) xmalloc(file_entry->size + 1);
76                         fread(buffer, 1, file_entry->size, src_stream);
77                         buffer[file_entry->size] = '\0';
78                         archive_offset += file_entry->size;
79                         return(buffer);
80                 }
81         }
82         else if (function & extract_all_to_fs) {
83                 struct stat oldfile;
84                 int stat_res;
85                 stat_res = lstat (full_name, &oldfile);
86                 if (stat_res == 0) { /* The file already exists */
87                         if ((function & extract_unconditional) || (oldfile.st_mtime < file_entry->mtime)) {
88                                 if (!S_ISDIR(oldfile.st_mode)) {
89                                         unlink(full_name); /* Directories might not be empty etc */
90                                 }
91                         } else {
92                                 if ((function & extract_quiet) != extract_quiet) {
93                                         error_msg("%s not created: newer or same age file exists", file_entry->name);
94                                 }
95                                 seek_sub_file(src_stream, file_entry->size);
96                                 return (NULL);
97                         }
98                 }
99                 if (function & extract_create_leading_dirs) { /* Create leading directories with default umask */
100                         char *buf, *parent;
101                         buf = xstrdup(full_name);
102                         parent = dirname(buf);
103                         if (make_directory (parent, -1, FILEUTILS_RECUR) != 0) {
104                                 if ((function & extract_quiet) != extract_quiet) {
105                                         error_msg("couldn't create leading directories");
106                                 }
107                         }
108                         free (buf);
109                 }
110                 switch(file_entry->mode & S_IFMT) {
111                         case S_IFREG:
112                                 if (file_entry->link_name) { /* Found a cpio hard link */
113                                         if (link(file_entry->link_name, full_name) != 0) {
114                                                 if ((function & extract_quiet) != extract_quiet) {
115                                                         perror_msg("Cannot link from %s to '%s'",
116                                                                 file_entry->name, file_entry->link_name);
117                                                 }
118                                         }
119                                 } else {
120                                         if ((dst_stream = wfopen(full_name, "w")) == NULL) {
121                                                 seek_sub_file(src_stream, file_entry->size);
122                                                 return NULL;
123                                         }
124                                         archive_offset += file_entry->size;
125                                         if (file_entry->extract_func) file_entry->extract_func(src_stream, dst_stream);
126                                         else copy_file_chunk(src_stream, dst_stream, file_entry->size);                 
127                                         fclose(dst_stream);
128                                 }
129                                 break;
130                         case S_IFDIR:
131                                 if (stat_res != 0) {
132                                         if (mkdir(full_name, file_entry->mode) < 0) {
133                                                 if ((function & extract_quiet) != extract_quiet) {
134                                                         perror_msg("extract_archive: %s", full_name);
135                                                 }
136                                         }
137                                 }
138                                 break;
139                         case S_IFLNK:
140                                 if (symlink(file_entry->link_name, full_name) < 0) {
141                                         if ((function & extract_quiet) != extract_quiet) {
142                                                 perror_msg("Cannot create symlink from %s to '%s'", file_entry->name, file_entry->link_name);
143                                         }
144                                         return NULL;
145                                 }
146                                 break;
147                         case S_IFSOCK:
148                         case S_IFBLK:
149                         case S_IFCHR:
150                         case S_IFIFO:
151                                 if (mknod(full_name, file_entry->mode, file_entry->device) == -1) {
152                                         if ((function & extract_quiet) != extract_quiet) {
153                                                 perror_msg("Cannot create node %s", file_entry->name);
154                                         }
155                                         return NULL;
156                                 }
157                                 break;
158                 }
159
160                 /* Changing a symlink's properties normally changes the properties of the 
161                  * file pointed to, so dont try and change the date or mode, lchown does
162                  * does the right thing, but isnt available in older versions of libc */
163                 if (S_ISLNK(file_entry->mode)) {
164 #if (__GLIBC__ > 2) && (__GLIBC_MINOR__ > 1)
165                         lchown(full_name, file_entry->uid, file_entry->gid);
166 #endif
167                 } else {
168                         if (function & extract_preserve_date) {
169                                 t.actime = file_entry->mtime;
170                                 t.modtime = file_entry->mtime;
171                                 utime(full_name, &t);
172                         }
173                         chmod(full_name, file_entry->mode);
174                         chown(full_name, file_entry->uid, file_entry->gid);
175                 }
176         } else {
177                 /* If we arent extracting data we have to skip it, 
178                  * if data size is 0 then then just do it anyway
179                  * (saves testing for it) */
180                 seek_sub_file(src_stream, file_entry->size);
181         }
182
183         /* extract_list and extract_verbose_list can be used in conjunction
184          * with one of the above four extraction functions, so do this seperately */
185         if (function & extract_verbose_list) {
186                 fprintf(out_stream, "%s %d/%d %8d %s ", mode_string(file_entry->mode), 
187                         file_entry->uid, file_entry->gid,
188                         (int) file_entry->size, time_string(file_entry->mtime));
189         }
190         if ((function & extract_list) || (function & extract_verbose_list)){
191                 /* fputs doesnt add a trailing \n, so use fprintf */
192                 fprintf(out_stream, "%s\n", full_name);
193         }
194
195         free(full_name);
196
197         return(NULL); /* Maybe we should say if failed */
198 }
199
200 char *unarchive(FILE *src_stream, FILE *out_stream, file_header_t *(*get_headers)(FILE *),
201         const int extract_function, const char *prefix, char **include_name, char **exclude_name)
202 {
203         file_header_t *file_entry;
204         int extract_flag = TRUE;
205         int i;
206         char *buffer = NULL;
207 #ifdef CONFIG_FEATURE_UNARCHIVE_TAPE
208         int pid, tape_pipe[2];
209
210         if (pipe(tape_pipe) != 0) error_msg_and_die("Can't create pipe\n");
211         if ((pid = fork()) == -1) error_msg_and_die("Fork failed\n");
212         if (pid==0) { /* child process */
213             close(tape_pipe[0]); /* We don't wan't to read from the pipe */
214             copyfd(fileno(src_stream), tape_pipe[1]);
215             close(tape_pipe[1]); /* Send EOF */
216             exit(0);
217             /* notreached */
218         }
219         close(tape_pipe[1]); /* Don't want to write down the pipe */
220         fclose(src_stream);
221         src_stream = fdopen(tape_pipe[0], "r");
222 #endif
223         archive_offset = 0;
224         while ((file_entry = get_headers(src_stream)) != NULL) {
225
226                 if (include_name != NULL) {
227                         extract_flag = FALSE;
228                         for(i = 0; include_name[i] != 0; i++) {
229                                 if (fnmatch(include_name[i], file_entry->name, FNM_LEADING_DIR) == 0) {
230                                         extract_flag = TRUE;
231                                         break;
232                                 }
233                         }
234                 } else {
235                         extract_flag = TRUE;
236                 }
237
238                 /* If the file entry is in the exclude list dont extract it */
239                 if (exclude_name != NULL) {
240                         for(i = 0; exclude_name[i] != 0; i++) {
241                                 if (fnmatch(exclude_name[i], file_entry->name, FNM_LEADING_DIR) == 0) {
242                                         extract_flag = FALSE;
243                                         break;
244                                 }
245                         }
246                 }
247
248                 if (extract_flag) {
249                         buffer = extract_archive(src_stream, out_stream, file_entry, extract_function, prefix);
250                 } else {
251                         /* seek past the data entry */
252                         seek_sub_file(src_stream, file_entry->size);
253                 }
254                 free(file_entry->name);
255                 free(file_entry->link_name);
256                 free(file_entry);
257         }
258 #ifdef CONFIG_FEATURE_UNARCHIVE_TAPE
259         kill(pid, SIGTERM);
260         waitpid(pid, NULL, 0);
261 #endif
262         return(buffer);
263 }