don't change of directory anymore
[src/app-framework-main.git] / src / wgtpkg-zip.c
1 /*
2  Copyright 2015 IoT.bzh
3
4  Licensed under the Apache License, Version 2.0 (the "License");
5  you may not use this file except in compliance with the License.
6  You may obtain a copy of the License at
7
8      http://www.apache.org/licenses/LICENSE-2.0
9
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15 */
16
17 #define _BSD_SOURCE /* see readdir */
18
19 #include <limits.h>
20 #include <zip.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <errno.h>
25 #include <assert.h>
26 #include <dirent.h>
27 #include <string.h>
28 #include <syslog.h>
29 #include <unistd.h>
30
31 #include "wgtpkg.h"
32
33
34 #if !defined(MODE_OF_FILE_CREATION)
35 #define MODE_OF_FILE_CREATION 0640
36 #endif
37 #if !defined(MODE_OF_DIRECTORY_CREATION)
38 #define MODE_OF_DIRECTORY_CREATION 0750
39 #endif
40
41 static int is_valid_filename(const char *filename)
42 {
43         int lastsp = 0;
44         int index = 0;
45         unsigned char c;
46
47         c = (unsigned char)filename[index];
48         while (c) {
49                 if ((c < 0x1f)
50                  || ((lastsp = (c == 0x20)) && index == 0)
51                  || c == 0x7f || c == 0x3c || c == 0x3e
52                  || c == 0x3a || c == 0x22 
53                  || c == 0x5c || c == 0x7c || c == 0x3f
54                  || c == 0x2a || c == 0x5e || c == 0x60
55                  || c == 0x7b || c == 0x7d || c == 0x21)
56                         return 0;
57                 c = (unsigned char)filename[++index];
58         }
59         return !lastsp;
60 }
61
62 static int create_directory(char *file, int mode)
63 {
64         int rc;
65         char *last = strrchr(file, '/');
66         if (last != NULL)
67                 *last = 0;
68         rc = mkdirat(workdirfd, file, mode);
69         if (rc) {
70                 if (errno == EEXIST)
71                         rc = 0;
72                 else if (errno == ENOENT) {
73                         rc = create_directory(file, mode);
74                         if (!rc)
75                                 rc = mkdirat(workdirfd, file, mode);
76                 }
77         }
78         if (rc)
79                 syslog(LOG_ERR, "can't create directory %s", file);
80         if (last != NULL)
81                 *last = '/';
82         return rc;
83 }
84
85 static int create_file(char *file, int fmode, int dmode)
86 {
87         int fd = openat(workdirfd, file, O_CREAT|O_WRONLY|O_TRUNC, fmode);
88         if (fd < 0 && errno == ENOENT) {
89                 if (!create_directory(file, dmode))
90                         fd = openat(workdirfd, file, O_CREAT|O_WRONLY|O_TRUNC, fmode);
91         }
92         if (fd < 0)
93                 syslog(LOG_ERR, "can't create file %s", file);
94         return fd;
95 }
96
97 /* read (extract) 'zipfile' in current directory */
98 int zread(const char *zipfile, unsigned long long maxsize)
99 {
100         struct filedesc *fdesc;
101         int err, fd, len;
102         struct zip *zip;
103         zip_int64_t z64;
104         unsigned int count, index;
105         struct zip_file *zfile;
106         struct zip_stat zstat;
107         char buffer[32768];
108         ssize_t sizr, sizw;
109         size_t esize;
110
111         /* open the zip file */
112         zip = zip_open(zipfile, ZIP_CHECKCONS, &err);
113         if (!zip) {
114                 syslog(LOG_ERR, "Can't connect to file %s", zipfile);
115                 return -1;
116         }
117
118         z64 = zip_get_num_entries(zip, 0);
119         if (z64 < 0 || z64 > UINT_MAX) {
120                 syslog(LOG_ERR, "too many entries in %s", zipfile);
121                 goto error;
122         }
123         count = (unsigned int)z64;
124
125         /* records the files */
126         file_reset();
127         esize = 0;
128         for (index = 0 ; index < count ; index++) {
129                 err = zip_stat_index(zip, index, ZIP_FL_ENC_GUESS, &zstat);
130                 /* check the file name */
131                 if (!is_valid_filename(zstat.name)) {
132                         syslog(LOG_ERR, "invalid entry %s found in %s", zstat.name, zipfile);
133                         goto error;
134                 }
135                 if (zstat.name[0] == '/') {
136                         syslog(LOG_ERR, "absolute entry %s found in %s", zstat.name, zipfile);
137                         goto error;
138                 }
139                 len = strlen(zstat.name);
140                 if (len == 0) {
141                         syslog(LOG_ERR, "empty entry found in %s", zipfile);
142                         goto error;
143                 }
144                 if (zstat.size == 0) {
145                         /* directory name */
146                         if (zstat.name[len - 1] != '/') {
147                                 syslog(LOG_ERR, "bad directory name %s in %s", zstat.name, zipfile);
148                                 goto error;
149                         }
150                         /* record */
151                         fdesc = file_add_directory(zstat.name);
152                 } else {
153                         /* directory name */
154                         if (zstat.name[len - 1] == '/') {
155                                 syslog(LOG_ERR, "bad file name %s in %s", zstat.name, zipfile);
156                                 goto error;
157                         }
158                         /* get the size */
159                         esize += zstat.size;
160                         /* record */
161                         fdesc = file_add_file(zstat.name);
162                 }
163                 if (!fdesc)
164                         goto error;
165                 fdesc->zindex = index;
166         }
167
168         /* check the size */
169         if (maxsize && esize > maxsize) {
170                 syslog(LOG_ERR, "extracted size %zu greater than allowed size %llu", esize, maxsize);
171                 goto error;
172         }
173
174         /* unpack the recorded files */
175         assert(count == file_count());
176         for (index = 0 ; index < count ; index++) {
177                 fdesc = file_of_index(index);
178                 assert(fdesc != NULL);
179                 err = zip_stat_index(zip, fdesc->zindex, ZIP_FL_ENC_GUESS, &zstat);
180                 assert(zstat.name[0] != '/');
181                 if (zstat.size == 0) {
182                         /* directory name */
183                         err = create_directory((char*)zstat.name, MODE_OF_DIRECTORY_CREATION);
184                         if (err && errno != EEXIST)
185                                 goto error;
186                 } else {
187                         /* file name */
188                         zfile = zip_fopen_index(zip, fdesc->zindex, 0);
189                         if (!zfile) {
190                                 syslog(LOG_ERR, "Can't open %s in %s", zstat.name, zipfile);
191                                 goto error;
192                         }
193                         fd = create_file((char*)zstat.name, MODE_OF_FILE_CREATION, MODE_OF_DIRECTORY_CREATION);
194                         if (fd < 0)
195                                 goto errorz;
196                         /* extract */
197                         z64 = zstat.size;
198                         while (z64) {
199                                 sizr = zip_fread(zfile, buffer, sizeof buffer);
200                                 if (sizr < 0) {
201                                         syslog(LOG_ERR, "error while reading %s in %s", zstat.name, zipfile);
202                                         goto errorzf;
203                                 }
204                                 sizw = write(fd, buffer, sizr);
205                                 if (sizw < 0) {
206                                         syslog(LOG_ERR, "error while writing %s", zstat.name);
207                                         goto errorzf;
208                                 }
209                                 z64 -= sizw;
210                         }
211                         close(fd);
212                         zip_fclose(zfile);
213                 }
214         }
215
216         zip_close(zip);
217         return 0;
218
219 errorzf:
220         close(fd);
221 errorz:
222         zip_fclose(zfile);
223 error:
224         zip_close(zip);
225         return -1;
226 }
227
228 struct zws {
229         struct zip *zip;
230         char name[PATH_MAX];
231         char buffer[32768];
232 };
233
234 static int zwr(struct zws *zws, int offset)
235 {
236         int len, err, fd;
237         DIR *dir;
238         struct dirent *ent;
239         zip_int64_t z64;
240         struct zip_source *zsrc;
241         FILE *fp;
242
243         fd = openat(workdirfd, offset ? zws->name : ".", O_DIRECTORY|O_RDONLY);
244         if (fd < 0) {
245                 syslog(LOG_ERR, "opendir %.*s failed in zwr", offset, zws->name);
246                 return -1;
247         }
248         dir = fdopendir(fd);
249         if (!dir) {
250                 close(fd);
251                 syslog(LOG_ERR, "opendir %.*s failed in zwr", offset, zws->name);
252                 return -1;
253         }
254
255         if (offset != 0)
256                 zws->name[offset++] = '/';
257
258         ent = readdir(dir);
259         while (ent != NULL) {
260                 len = strlen(ent->d_name);
261                 if (ent->d_name[0] == '.' && (len == 1 || 
262                         (ent->d_name[1] == '.' && len == 2)))
263                         ;
264                 else if (offset + len >= sizeof(zws->name)) {
265                         syslog(LOG_ERR, "name too long in zwr");
266                         errno = ENAMETOOLONG;
267                         goto error;
268                 } else {
269                         memcpy(zws->name + offset, ent->d_name, 1+len);
270                         if (!is_valid_filename(ent->d_name)) {
271                                 syslog(LOG_ERR, "invalid name %s", zws->name);
272                                 goto error;
273                         }
274                         switch (ent->d_type) {
275                         case DT_DIR:
276                                 z64 = zip_dir_add(zws->zip, zws->name, ZIP_FL_ENC_UTF_8);
277                                 if (z64 < 0) {
278                                         syslog(LOG_ERR, "zip_dir_add of %s failed", zws->name);
279                                         goto error;
280                                 }
281                                 err = zwr(zws, offset + len);
282                                 if (err)
283                                         goto error;
284                                 break;
285                         case DT_REG:
286                                 fd = openat(workdirfd, zws->name, O_RDONLY);
287                                 if (fd < 0) {
288                                         syslog(LOG_ERR, "openat of %s failed", zws->name);
289                                         goto error;
290                                 }
291                                 fp = fdopen(fd, "r");
292                                 if (fp == NULL) {
293                                         syslog(LOG_ERR, "fdopen of %s failed", zws->name);
294                                         close(fd);
295                                         goto error;
296                                 }
297                                 zsrc = zip_source_filep(zws->zip, fp, 0, 0);
298                                 if (zsrc == NULL) {
299                                         syslog(LOG_ERR, "zip_source_file of %s failed", zws->name);
300                                         fclose(fp);
301                                         goto error;
302                                 }
303                                 z64 = zip_file_add(zws->zip, zws->name, zsrc, ZIP_FL_ENC_UTF_8);
304                                 if (z64 < 0) {
305                                         syslog(LOG_ERR, "zip_file_add of %s failed", zws->name);
306                                         zip_source_free(zsrc);
307                                         goto error;
308                                 }
309                                 break;
310                         default:
311                                 break;
312                         }
313                 }
314                 ent = readdir(dir);
315         }
316
317         closedir(dir);
318         return 0;
319 error:
320         closedir(dir);
321         return -1;
322 }
323
324 /* write (pack) content of the current directory in 'zipfile' */
325 int zwrite(const char *zipfile)
326 {
327         int err;
328         struct zws zws;
329
330         zws.zip = zip_open(zipfile, ZIP_CREATE|ZIP_TRUNCATE, &err);
331         if (!zws.zip) {
332                 syslog(LOG_ERR, "Can't open %s for write", zipfile);
333                 return -1;
334         }
335
336         err = zwr(&zws, 0);
337         zip_close(zws.zip);
338         return err;
339 }
340
341
342 #if defined(TEST_READ)
343 int main(int ac, char **av)
344 {
345         for(av++ ; *av ; av++)
346                 zread(*av);
347         return 0;
348 }
349 #endif
350
351 #if defined(TEST_WRITE)
352 int main(int ac, char **av)
353 {
354         for(av++ ; *av ; av++)
355                 zwrite(*av);
356         return 0;
357 }
358 #endif
359