916d0cd0b6fa98ba99222486eaa15e0a549367d7
[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 char *getbin(const char *progname)
361 {
362         char name[PATH_MAX];
363         char *path;
364         int i;
365
366         if (progname[0] == '/')
367                 return access(progname, X_OK) ? NULL : strdup(progname);
368
369         path = getenv("PATH");
370         while(path && *path) {
371                 for (i = 0 ; path[i] && path[i] != ':' ; i++)
372                         name[i] = path[i];
373                 path += i + !!path[i];
374                 name[i] = '/';
375                 strcpy(name + i + 1, progname);
376                 if (access(name, X_OK) == 0)
377                         return realpath(name, NULL);
378         }
379         return NULL;
380 }
381
382 static int zrun(const char *name, const char *args[])
383 {
384         int rc;
385         siginfo_t si;
386         char *binary;
387
388         binary = getbin(name);
389         if (binary == NULL) {
390                 ERROR("error while forking in zrun: can't find %s", name);
391                 return -1;
392         }
393
394         rc = fork();
395         if (rc == 0) {
396                 rc = execve(binary, (char * const*)args, environ);
397                 ERROR("can't execute %s in zrun: %m", args[0]);
398                 _exit(1);
399                 return rc;
400         }
401
402         free(binary);
403         if (rc < 0) {
404                 /* can't fork */
405                 ERROR("error while forking in zrun: %m");
406                 return rc;
407         }
408
409         /* wait termination of the child */
410         rc = waitid(P_PID, (id_t)rc, &si, WEXITED);
411         if (rc)
412                 ERROR("unexpected wait status in zrun of %s: %m", args[0]);
413         else if (si.si_code != CLD_EXITED)
414                 ERROR("unexpected termination status of %s in zrun", args[0]);
415         else if (si.si_status != 0)
416                 ERROR("child for %s terminated with error code %d in zwrite", args[0], si.si_status);
417         else
418                 return 0;
419         return -1;
420 }
421
422 /* read (extract) 'zipfile' in current directory */
423 int zread(const char *zipfile, unsigned long long maxsize)
424 {
425         int rc;
426         const char *args[6];
427
428         args[0] = "unzip";
429         args[1] = "-q";
430         args[2] = "-d";
431         args[3] = workdir;
432         args[4] = zipfile;
433         args[5] = NULL;
434
435         file_reset();
436         rc = zrun(args[0], args);
437         if (!rc)
438                 rc = fill_files();
439         return rc;
440 }
441
442 /* write (pack) content of the current directory in 'zipfile' */
443 int zwrite(const char *zipfile)
444 {
445         const char *args[6];
446
447         args[0] = "zip";
448         args[1] = "-q";
449         args[2] = "-r";
450         args[3] = zipfile;
451         args[4] = workdir;
452         args[5] = NULL;
453
454         return zrun(args[0], args);
455 }
456
457 #endif
458 /***********************************************************
459 *        TESTING
460 ***********************************************************/
461
462 #if defined(TEST_READ)
463 int main(int ac, char **av)
464 {
465         for(av++ ; *av ; av++)
466                 zread(*av, 0);
467         return 0;
468 }
469 #endif
470
471 #if defined(TEST_WRITE)
472 int main(int ac, char **av)
473 {
474         for(av++ ; *av ; av++)
475                 zwrite(*av);
476         return 0;
477 }
478 #endif
479