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