6fb839d213c81a498ce8ca2e208a32dbde928c95
[src/app-framework-main.git] / src / wgtpkg-zip.c
1 /*
2  Copyright 2015, 2016 IoT.bzh
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
6  Licensed under the Apache License, Version 2.0 (the "License");
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 */
18
19 #define _DEFAULT_SOURCE
20
21 #include <limits.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <fcntl.h>
25 #include <errno.h>
26 #include <assert.h>
27 #include <dirent.h>
28 #include <string.h>
29 #include <unistd.h>
30
31 #include "verbose.h"
32 #include "wgtpkg-files.h"
33 #include "wgtpkg-workdir.h"
34 #include "wgtpkg-zip.h"
35
36 #define MODE_OF_FILE_CREATION 0644
37 #define MODE_OF_DIRECTORY_CREATION 0755
38
39 #if !defined(USE_LIBZIP)
40 #       define USE_LIBZIP 1
41 #endif
42
43 /***********************************************************
44  *        USING LIBZIP
45  ***********************************************************/
46 #if USE_LIBZIP
47
48 #include <zip.h>
49
50 static int is_valid_filename(const char *filename)
51 {
52         int lastsp = 0;
53         int index = 0;
54         unsigned char c;
55
56         c = (unsigned char)filename[index];
57         while (c) {
58                 if ((c < 0x1f)
59                  || ((lastsp = (c == 0x20)) && index == 0)
60                  || c == 0x7f || c == 0x3c || c == 0x3e
61                  || c == 0x3a || c == 0x22 
62                  || c == 0x5c || c == 0x7c || c == 0x3f
63                  || c == 0x2a || c == 0x5e || c == 0x60
64                  || c == 0x7b || c == 0x7d || c == 0x21)
65                         return 0;
66                 c = (unsigned char)filename[++index];
67         }
68         return !lastsp;
69 }
70
71 static int create_directory(char *file, mode_t mode)
72 {
73         int rc;
74         char *last = strrchr(file, '/');
75         if (last != NULL)
76                 *last = 0;
77         rc = mkdirat(workdirfd, file, mode);
78         if (rc) {
79                 if (errno == EEXIST)
80                         rc = 0;
81                 else if (errno == ENOENT) {
82                         rc = create_directory(file, mode);
83                         if (!rc)
84                                 rc = mkdirat(workdirfd, file, mode);
85                 }
86         }
87         if (rc)
88                 ERROR("can't create directory %s", file);
89         if (last != NULL)
90                 *last = '/';
91         return rc;
92 }
93
94 static int create_file(char *file, int fmode, mode_t dmode)
95 {
96         int fd = openat(workdirfd, file, O_CREAT|O_WRONLY|O_TRUNC, fmode);
97         if (fd < 0 && errno == ENOENT) {
98                 if (!create_directory(file, dmode))
99                         fd = openat(workdirfd, file, O_CREAT|O_WRONLY|O_TRUNC, fmode);
100         }
101         if (fd < 0)
102                 ERROR("can't create file %s", file);
103         return fd;
104 }
105
106 /* read (extract) 'zipfile' in current directory */
107 int zread(const char *zipfile, unsigned long long maxsize)
108 {
109         struct filedesc *fdesc;
110         int err, fd;
111         size_t len;
112         struct zip *zip;
113         zip_int64_t z64;
114         zip_uint64_t uz64;
115         unsigned int count, index;
116         struct zip_file *zfile;
117         struct zip_stat zstat;
118         char buffer[32768];
119         ssize_t sizr, sizw;
120         zip_uint64_t esize;
121
122         /* open the zip file */
123         zip = zip_open(zipfile, ZIP_CHECKCONS, &err);
124         if (!zip) {
125                 ERROR("Can't connect to file %s", zipfile);
126                 return -1;
127         }
128
129         z64 = zip_get_num_entries(zip, 0);
130         if (z64 < 0 || z64 > UINT_MAX) {
131                 ERROR("too many entries in %s", zipfile);
132                 goto error;
133         }
134         count = (unsigned int)z64;
135
136         /* records the files */
137         file_reset();
138         esize = 0;
139         for (index = 0 ; index < count ; index++) {
140                 err = zip_stat_index(zip, index, ZIP_FL_ENC_GUESS, &zstat);
141                 /* check the file name */
142                 if (!is_valid_filename(zstat.name)) {
143                         ERROR("invalid entry %s found in %s", zstat.name, zipfile);
144                         goto error;
145                 }
146                 if (zstat.name[0] == '/') {
147                         ERROR("absolute entry %s found in %s", zstat.name, zipfile);
148                         goto error;
149                 }
150                 len = strlen(zstat.name);
151                 if (len == 0) {
152                         ERROR("empty entry found in %s", zipfile);
153                         goto error;
154                 }
155                 if (zstat.name[len - 1] == '/')
156                         /* record */
157                         fdesc = file_add_directory(zstat.name);
158                 else {
159                         /* get the size */
160                         esize += zstat.size;
161                         /* record */
162                         fdesc = file_add_file(zstat.name);
163                 }
164                 if (!fdesc)
165                         goto error;
166                 fdesc->zindex = index;
167         }
168
169         /* check the size */
170         if (maxsize && esize > maxsize) {
171                 ERROR("extracted size %zu greater than allowed size %llu", esize, maxsize);
172                 goto error;
173         }
174
175         /* unpack the recorded files */
176         assert(count == file_count());
177         for (index = 0 ; index < count ; index++) {
178                 fdesc = file_of_index(index);
179                 assert(fdesc != NULL);
180                 err = zip_stat_index(zip, fdesc->zindex, ZIP_FL_ENC_GUESS, &zstat);
181                 assert(zstat.name[0] != '/');
182                 len = strlen(zstat.name);
183                 assert(len > 0);
184                 if (zstat.name[len - 1] == '/') {
185                         /* directory name */
186                         err = create_directory((char*)zstat.name, MODE_OF_DIRECTORY_CREATION);
187                         if (err && errno != EEXIST)
188                                 goto error;
189                 } else {
190                         /* file name */
191                         zfile = zip_fopen_index(zip, fdesc->zindex, 0);
192                         if (!zfile) {
193                                 ERROR("Can't open %s in %s", zstat.name, zipfile);
194                                 goto error;
195                         }
196                         fd = create_file((char*)zstat.name, MODE_OF_FILE_CREATION, MODE_OF_DIRECTORY_CREATION);
197                         if (fd < 0)
198                                 goto errorz;
199                         /* extract */
200                         uz64 = zstat.size;
201                         while (uz64) {
202                                 sizr = (ssize_t)zip_fread(zfile, buffer, sizeof buffer);
203                                 if (sizr < 0) {
204                                         ERROR("error while reading %s in %s", zstat.name, zipfile);
205                                         goto errorzf;
206                                 }
207                                 sizw = write(fd, buffer, (size_t)sizr);
208                                 if (sizw < 0) {
209                                         ERROR("error while writing %s", zstat.name);
210                                         goto errorzf;
211                                 }
212                                 uz64 -= (size_t)sizw;
213                         }
214                         close(fd);
215                         zip_fclose(zfile);
216                 }
217         }
218
219         zip_close(zip);
220         return 0;
221
222 errorzf:
223         close(fd);
224 errorz:
225         zip_fclose(zfile);
226 error:
227         zip_close(zip);
228         return -1;
229 }
230
231 struct zws {
232         struct zip *zip;
233         char name[PATH_MAX];
234         char buffer[32768];
235 };
236
237 static int zwr(struct zws *zws, size_t offset)
238 {
239         int err, fd;
240         size_t len;
241         DIR *dir;
242         struct dirent *ent;
243         zip_int64_t z64;
244         struct zip_source *zsrc;
245         FILE *fp;
246
247         fd = openat(workdirfd, offset ? zws->name : ".", O_DIRECTORY|O_RDONLY);
248         if (fd < 0) {
249                 ERROR("opendir %.*s failed in zwr", (int)offset, zws->name);
250                 return -1;
251         }
252         dir = fdopendir(fd);
253         if (!dir) {
254                 close(fd);
255                 ERROR("opendir %.*s failed in zwr", (int)offset, zws->name);
256                 return -1;
257         }
258
259         if (offset != 0)
260                 zws->name[offset++] = '/';
261
262         ent = readdir(dir);
263         while (ent != NULL) {
264                 len = strlen(ent->d_name);
265                 if (ent->d_name[0] == '.' && (len == 1 || 
266                         (ent->d_name[1] == '.' && len == 2)))
267                         ;
268                 else if (offset + len >= sizeof(zws->name)) {
269                         ERROR("name too long in zwr");
270                         errno = ENAMETOOLONG;
271                         goto error;
272                 } else {
273                         memcpy(zws->name + offset, ent->d_name, 1+len);
274                         if (!is_valid_filename(ent->d_name)) {
275                                 ERROR("invalid name %s", zws->name);
276                                 goto error;
277                         }
278                         switch (ent->d_type) {
279                         case DT_DIR:
280                                 z64 = zip_dir_add(zws->zip, zws->name, ZIP_FL_ENC_UTF_8);
281                                 if (z64 < 0) {
282                                         ERROR("zip_dir_add of %s failed", zws->name);
283                                         goto error;
284                                 }
285                                 err = zwr(zws, offset + len);
286                                 if (err)
287                                         goto error;
288                                 break;
289                         case DT_REG:
290                                 fd = openat(workdirfd, zws->name, O_RDONLY);
291                                 if (fd < 0) {
292                                         ERROR("openat of %s failed", zws->name);
293                                         goto error;
294                                 }
295                                 fp = fdopen(fd, "r");
296                                 if (fp == NULL) {
297                                         ERROR("fdopen of %s failed", zws->name);
298                                         close(fd);
299                                         goto error;
300                                 }
301                                 zsrc = zip_source_filep(zws->zip, fp, 0, 0);
302                                 if (zsrc == NULL) {
303                                         ERROR("zip_source_file of %s failed", zws->name);
304                                         fclose(fp);
305                                         goto error;
306                                 }
307                                 z64 = zip_file_add(zws->zip, zws->name, zsrc, ZIP_FL_ENC_UTF_8);
308                                 if (z64 < 0) {
309                                         ERROR("zip_file_add of %s failed", zws->name);
310                                         zip_source_free(zsrc);
311                                         goto error;
312                                 }
313                                 break;
314                         default:
315                                 break;
316                         }
317                 }
318                 ent = readdir(dir);
319         }
320
321         closedir(dir);
322         return 0;
323 error:
324         closedir(dir);
325         return -1;
326 }
327
328 /* write (pack) content of the current directory in 'zipfile' */
329 int zwrite(const char *zipfile)
330 {
331         int err;
332         struct zws zws;
333
334         zws.zip = zip_open(zipfile, ZIP_CREATE|ZIP_TRUNCATE, &err);
335         if (!zws.zip) {
336                 ERROR("Can't open %s for write", zipfile);
337                 return -1;
338         }
339
340         err = zwr(&zws, 0);
341         zip_close(zws.zip);
342         return err;
343 }
344
345 /***********************************************************
346  *        NOT USING LIBZIP: FORKING
347  ***********************************************************/
348 #else
349
350 #include <sys/wait.h>
351 #include <stdlib.h>
352
353 extern char **environ;
354
355 static char *getbin(const char *progname)
356 {
357         char name[PATH_MAX];
358         char *path;
359         int i;
360
361         if (progname[0] == '/')
362                 return access(progname, X_OK) ? NULL : strdup(progname);
363
364         path = getenv("PATH");
365         while(path && *path) {
366                 for (i = 0 ; path[i] && path[i] != ':' ; i++)
367                         name[i] = path[i];
368                 path += i + !!path[i];
369                 name[i] = '/';
370                 strcpy(name + i + 1, progname);
371                 if (access(name, X_OK) == 0)
372                         return realpath(name, NULL);
373         }
374         return NULL;
375 }
376
377 static int zrun(const char *name, const char *args[])
378 {
379         int rc;
380         siginfo_t si;
381         char *binary;
382
383         binary = getbin(name);
384         if (binary == NULL) {
385                 ERROR("error while forking in zrun: can't find %s", name);
386                 return -1;
387         }
388
389         rc = fork();
390         if (rc == 0) {
391                 rc = execve(binary, (char * const*)args, environ);
392                 ERROR("can't execute %s in zrun: %m", args[0]);
393                 _exit(1);
394                 return rc;
395         }
396
397         free(binary);
398         if (rc < 0) {
399                 /* can't fork */
400                 ERROR("error while forking in zrun: %m");
401                 return rc;
402         }
403
404         /* wait termination of the child */
405         rc = waitid(P_PID, (id_t)rc, &si, WEXITED);
406         if (rc)
407                 ERROR("unexpected wait status in zrun of %s: %m", args[0]);
408         else if (si.si_code != CLD_EXITED)
409                 ERROR("unexpected termination status of %s in zrun", args[0]);
410         else if (si.si_status != 0)
411                 ERROR("child for %s terminated with error code %d in zwrite", args[0], si.si_status);
412         else
413                 return 0;
414         return -1;
415 }
416
417 /* read (extract) 'zipfile' in current directory */
418 int zread(const char *zipfile, unsigned long long maxsize)
419 {
420         int rc;
421         const char *args[6];
422
423         args[0] = "unzip";
424         args[1] = "-q";
425         args[2] = "-d";
426         args[3] = workdir;
427         args[4] = zipfile;
428         args[5] = NULL;
429
430         file_reset();
431         rc = zrun(args[0], args);
432         if (!rc)
433                 rc = fill_files();
434         return rc;
435 }
436
437 /* write (pack) content of the current directory in 'zipfile' */
438 int zwrite(const char *zipfile)
439 {
440         const char *args[6];
441
442         args[0] = "zip";
443         args[1] = "-q";
444         args[2] = "-r";
445         args[3] = zipfile;
446         args[4] = workdir;
447         args[5] = NULL;
448
449         return zrun(args[0], args);
450 }
451
452 #endif
453 /***********************************************************
454 *        TESTING
455 ***********************************************************/
456
457 #if defined(TEST_READ)
458 int main(int ac, char **av)
459 {
460         for(av++ ; *av ; av++)
461                 zread(*av, 0);
462         return 0;
463 }
464 #endif
465
466 #if defined(TEST_WRITE)
467 int main(int ac, char **av)
468 {
469         for(av++ ; *av ; av++)
470                 zwrite(*av);
471         return 0;
472 }
473 #endif
474