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