wgtpkg-zip: Ensure zip will not complain
[src/app-framework-main.git] / src / wgtpkg-zip.c
1 /*
2  Copyright (C) 2015-2018 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         struct stat st;
247
248         fd = openat(workdirfd, offset ? zws->name : ".", O_DIRECTORY|O_RDONLY);
249         if (fd < 0) {
250                 ERROR("opendir %.*s failed in zwr", (int)offset, zws->name);
251                 return -1;
252         }
253         dir = fdopendir(fd);
254         if (!dir) {
255                 close(fd);
256                 ERROR("opendir %.*s failed in zwr", (int)offset, zws->name);
257                 return -1;
258         }
259
260         if (offset != 0)
261                 zws->name[offset++] = '/';
262
263         ent = readdir(dir);
264         while (ent != NULL) {
265                 len = strlen(ent->d_name);
266                 if (ent->d_name[0] == '.' && (len == 1 ||
267                         (ent->d_name[1] == '.' && len == 2)))
268                         ;
269                 else if (offset + len >= sizeof(zws->name)) {
270                         ERROR("name too long in zwr");
271                         errno = ENAMETOOLONG;
272                         goto error;
273                 } else {
274                         memcpy(zws->name + offset, ent->d_name, 1+len);
275                         if (!is_valid_filename(ent->d_name)) {
276                                 ERROR("invalid name %s", zws->name);
277                                 goto error;
278                         }
279                         if (ent->d_type == DT_UNKNOWN) {
280                                 fstatat(fd, ent->d_name, &st, 0);
281                                 if (S_ISREG(st.st_mode))
282                                         ent->d_type = DT_REG;
283                                 else if (S_ISDIR(st.st_mode))
284                                         ent->d_type = DT_DIR;
285                         }
286                         switch (ent->d_type) {
287                         case DT_DIR:
288                                 z64 = zip_dir_add(zws->zip, zws->name, ZIP_FL_ENC_UTF_8);
289                                 if (z64 < 0) {
290                                         ERROR("zip_dir_add of %s failed", zws->name);
291                                         goto error;
292                                 }
293                                 err = zwr(zws, offset + len);
294                                 if (err)
295                                         goto error;
296                                 break;
297                         case DT_REG:
298                                 fd = openat(workdirfd, zws->name, O_RDONLY);
299                                 if (fd < 0) {
300                                         ERROR("openat of %s failed", zws->name);
301                                         goto error;
302                                 }
303                                 fp = fdopen(fd, "r");
304                                 if (fp == NULL) {
305                                         ERROR("fdopen of %s failed", zws->name);
306                                         close(fd);
307                                         goto error;
308                                 }
309                                 zsrc = zip_source_filep(zws->zip, fp, 0, 0);
310                                 if (zsrc == NULL) {
311                                         ERROR("zip_source_file of %s failed", zws->name);
312                                         fclose(fp);
313                                         goto error;
314                                 }
315                                 z64 = zip_file_add(zws->zip, zws->name, zsrc, ZIP_FL_ENC_UTF_8);
316                                 if (z64 < 0) {
317                                         ERROR("zip_file_add of %s failed", zws->name);
318                                         zip_source_free(zsrc);
319                                         goto error;
320                                 }
321                                 break;
322                         default:
323                                 break;
324                         }
325                 }
326                 ent = readdir(dir);
327         }
328
329         closedir(dir);
330         return 0;
331 error:
332         closedir(dir);
333         return -1;
334 }
335
336 /* write (pack) content of the current directory in 'zipfile' */
337 int zwrite(const char *zipfile)
338 {
339         int err;
340         struct zws zws;
341
342         zws.zip = zip_open(zipfile, ZIP_CREATE|ZIP_TRUNCATE, &err);
343         if (!zws.zip) {
344                 ERROR("Can't open %s for write", zipfile);
345                 return -1;
346         }
347
348         err = zwr(&zws, 0);
349         zip_close(zws.zip);
350         return err;
351 }
352
353 /***********************************************************
354  *        NOT USING LIBZIP: FORKING
355  ***********************************************************/
356 #else
357
358 #include <sys/wait.h>
359 #include <stdlib.h>
360
361 extern char **environ;
362
363 static char *getbin(const char *progname)
364 {
365         char name[PATH_MAX];
366         char *path;
367         int i;
368
369         if (progname[0] == '/')
370                 return access(progname, X_OK) ? NULL : strdup(progname);
371
372         path = getenv("PATH");
373         while(path && *path) {
374                 for (i = 0 ; path[i] && path[i] != ':' ; i++)
375                         name[i] = path[i];
376                 path += i + !!path[i];
377                 name[i] = '/';
378                 strcpy(name + i + 1, progname);
379                 if (access(name, X_OK) == 0)
380                         return realpath(name, NULL);
381         }
382         return NULL;
383 }
384
385 static int zrun(const char *name, const char *args[])
386 {
387         int rc;
388         siginfo_t si;
389         char *binary;
390
391         binary = getbin(name);
392         if (binary == NULL) {
393                 ERROR("error while forking in zrun: can't find %s", name);
394                 return -1;
395         }
396
397         rc = fork();
398         if (rc == 0) {
399                 rc = execve(binary, (char * const*)args, environ);
400                 ERROR("can't execute %s in zrun: %m", args[0]);
401                 _exit(1);
402                 return rc;
403         }
404
405         free(binary);
406         if (rc < 0) {
407                 /* can't fork */
408                 ERROR("error while forking in zrun: %m");
409                 return rc;
410         }
411
412         /* wait termination of the child */
413         rc = waitid(P_PID, (id_t)rc, &si, WEXITED);
414         if (rc)
415                 ERROR("unexpected wait status in zrun of %s: %m", args[0]);
416         else if (si.si_code != CLD_EXITED)
417                 ERROR("unexpected termination status of %s in zrun", args[0]);
418         else if (si.si_status != 0)
419                 ERROR("child for %s terminated with error code %d in zwrite", args[0], si.si_status);
420         else
421                 return 0;
422         return -1;
423 }
424
425 /* read (extract) 'zipfile' in current directory */
426 int zread(const char *zipfile, unsigned long long maxsize)
427 {
428         int rc;
429         const char *args[6];
430
431         args[0] = "unzip";
432         args[1] = "-q";
433         args[2] = "-d";
434         args[3] = workdir;
435         args[4] = zipfile;
436         args[5] = NULL;
437
438         file_reset();
439         rc = zrun(args[0], args);
440         if (!rc)
441                 rc = fill_files();
442         return rc;
443 }
444
445 /* write (pack) content of the current directory in 'zipfile' */
446 int zwrite(const char *zipfile)
447 {
448         const char *args[6];
449
450         unlink(zipfile);
451         args[0] = "zip";
452         args[1] = "-q";
453         args[2] = "-r";
454         args[3] = zipfile;
455         args[4] = workdir;
456         args[5] = NULL;
457
458         return zrun(args[0], args);
459 }
460
461 #endif
462 /***********************************************************
463 *        TESTING
464 ***********************************************************/
465
466 #if defined(TEST_READ)
467 int main(int ac, char **av)
468 {
469         for(av++ ; *av ; av++)
470                 zread(*av, 0);
471         return 0;
472 }
473 #endif
474
475 #if defined(TEST_WRITE)
476 int main(int ac, char **av)
477 {
478         for(av++ ; *av ; av++)
479                 zwrite(*av);
480         return 0;
481 }
482 #endif
483