7f2c7e6d5265d25778802f615e8ed482725925b0
[src/app-framework-main.git] / src / wgtpkg-workdir.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 _GNU_SOURCE
18
19 #include <unistd.h>
20 #include <string.h>
21 #include <dirent.h>
22 #include <syslog.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <assert.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28
29 #include "verbose.h"
30 #include "wgtpkg.h"
31
32 static int mode = 0755;
33 char workdir[PATH_MAX] = { 0, };
34 int workdirfd = -1;
35
36 /* removes recursively the content of a directory */
37 static int clean_dirfd(int dirfd)
38 {
39         int cr, fd;
40         DIR *dir;
41         struct dirent *ent;
42         struct {
43                 struct dirent entry;
44                 char spare[PATH_MAX];
45         } entry;
46
47         dirfd = dup(dirfd);
48         if (dirfd < 0) {
49                 ERROR("failed to dup the dirfd");
50                 return -1;
51         }
52         dir = fdopendir(dirfd);
53         if (dir == NULL) {
54                 ERROR("fdopendir failed in clean_dirfd");
55                 return -1;
56         }
57
58         cr = -1;
59         for (;;) {
60                 if (readdir_r(dir, &entry.entry, &ent) != 0) {
61                         ERROR("readdir_r failed in clean_dirfd");
62                         goto error;
63                 }
64                 if (ent == NULL)
65                         break;
66                 if (ent->d_name[0] == '.' && (ent->d_name[1] == 0
67                                 || (ent->d_name[1] == '.' && ent->d_name[2] == 0)))
68                         continue;
69                 cr = unlinkat(dirfd, ent->d_name, 0);
70                 if (!cr)
71                         continue;
72                 if (errno != EISDIR) {
73                         ERROR("unlink of %s failed in clean_dirfd", ent->d_name);
74                         goto error;
75                 }
76                 fd = openat(dirfd, ent->d_name, O_DIRECTORY|O_RDONLY);
77                 if (fd < 0) {
78                         ERROR("opening directory %s failed in clean_dirfd", ent->d_name);
79                         goto error;
80                 }
81                 cr = clean_dirfd(fd);
82                 close(fd);
83                 if (cr)
84                         goto error;
85                 cr = unlinkat(dirfd, ent->d_name, AT_REMOVEDIR);
86                 if (cr) {
87                         ERROR("rmdir of %s failed in clean_dirfd", ent->d_name);
88                         goto error;
89                 }
90         }
91         cr = 0;
92 error:
93         closedir(dir);
94         return cr;
95 }
96
97 /* removes recursively the content of a directory */
98 static int clean_dir(const char *directory)
99 {
100         int fd, rc;
101
102         fd = openat(AT_FDCWD, directory, O_DIRECTORY|O_RDONLY);
103         if (fd < 0) {
104                 ERROR("opening directory %s failed in clean_dir", directory);
105                 return fd;
106         }
107         rc = clean_dirfd(fd);
108         close(fd);
109         return rc;
110 }
111
112 /* removes the working directory */
113 void remove_workdir()
114 {
115         assert(workdirfd >= 0);
116         clean_dirfd(workdirfd);
117         close(workdirfd);
118         workdirfd = -1;
119         rmdir(workdir);
120         workdir[0] = 0;
121 }
122
123 static int set_real_workdir(const char *name, int create)
124 {
125         int rc, dirfd;
126         size_t length;
127
128         /* check the length */
129         length = strlen(name);
130         if (length >= sizeof workdir) {
131                 ERROR("workdir name too long");
132                 errno = EINVAL;
133                 return -1;
134         }
135
136         /* opens the directory */
137         dirfd = openat(AT_FDCWD, name, O_DIRECTORY|O_RDONLY);
138         if (dirfd < 0) {
139                 if (!create || errno != ENOENT) {
140                         ERROR("no workdir %s", name);
141                         return -1;
142                 }
143                 rc = mkdir(name, mode);
144                 if (rc) {
145                         ERROR("can't create workdir %s", name);
146                         return -1;
147                 }
148                 dirfd = open(name, O_PATH|O_DIRECTORY);
149                 if (dirfd < 0) {
150                         ERROR("can't open workdir %s", name);
151                         return -1;
152                 }
153         }
154
155         /* close the previous directory if any */
156         if (workdirfd >= 0)
157                 close(workdirfd);
158         workdirfd = dirfd;
159         memcpy(workdir, name, 1+length);
160         return 0;
161 }
162
163 int set_workdir(const char *name, int create)
164 {
165         char *rp;
166         int rc;
167
168         if (name[0] == '/')
169                 return set_real_workdir(name, create);
170
171         rp = realpath(name, NULL);
172         if (!rp) {
173                 ERROR("realpath failed for %s", name);
174                 return -1;
175         }
176         rc = set_real_workdir(rp, create);
177         free(rp);
178         return rc;
179 }
180
181 static int make_real_workdir_base(const char *root, const char *prefix, int reuse)
182 {
183         int i, n, r, l;
184
185         n = snprintf(workdir, sizeof workdir, "%s/%s", root, prefix);
186         if (n >= sizeof workdir) {
187                 ERROR("workdir prefix too long");
188                 errno = EINVAL;
189                 return -1;
190         }
191         r = (int)(sizeof workdir) - n;
192
193         if (workdirfd >= 0)
194                 close(workdirfd);
195         workdirfd = -1;
196
197         /* create a temporary directory */
198         for (i = 0 ; ; i++) {
199                 if (i == INT_MAX) {
200                         ERROR("exhaustion of workdirs");
201                         return -1;
202                 }
203                 l = snprintf(workdir + n, r, "%d", i);
204                 if (l >= r) {
205                         ERROR("computed workdir too long");
206                         errno = EINVAL;
207                         return -1;
208                 }
209                 if (!mkdir(workdir, mode))
210                         break;
211                 if (errno != EEXIST) {
212                         ERROR("error in creation of workdir %s: %m", workdir);
213                         return -1;
214                 }
215                 if (reuse)
216                         break;
217         }
218         workdirfd = openat(AT_FDCWD, workdir, O_RDONLY|O_DIRECTORY);
219         if (workdirfd < 0) {
220                 ERROR("error in onnection to workdir %s: %m", workdir);
221                 rmdir(workdir);
222                 return -1;
223         }
224
225         return 0;
226 }
227
228 int make_workdir_base(const char *root, const char *prefix, int reuse)
229 {
230         char *rp;
231         int rc;
232
233         if (root[0] == '/')
234                 return make_real_workdir_base(root, prefix, reuse);
235
236         rp = realpath(root, NULL);
237         if (!rp) {
238                 ERROR("realpath failed for %s", root);
239                 return -1;
240         }
241         rc = make_real_workdir_base(rp, prefix, reuse);
242         free(rp);
243         return rc;
244 }
245
246 int make_workdir(int reuse)
247 {
248         return make_workdir_base(".", "PACK", reuse);
249 }
250
251 static int move_real_workdir(const char *dest, int parents, int force)
252 {
253         int rc, len, l;
254         struct stat s;
255         char *copy;
256         const char *iter;
257
258         /* check length */
259         if (strlen(dest) >= sizeof workdir) {
260                 ERROR("destination dirname too long");
261                 errno = EINVAL;
262                 return -1;
263         }
264
265         /* if an existing directory exist remove it if force */
266         rc = stat(dest, &s);
267         if (rc == 0) {
268                 if (!S_ISDIR(s.st_mode)) {
269                         ERROR("in move_real_workdir, can't overwrite regular file %s", dest);
270                         errno = EEXIST;
271                         return -1;
272                 }
273                 if (!force) {
274                         ERROR("in move_real_workdir, can't overwrite regular file %s", dest);
275                         errno = EEXIST;
276                         return -1;
277                 }
278                 rc = clean_dir(dest);
279                 if (rc) {
280                         ERROR("in move_real_workdir, can't clean dir %s", dest);
281                         return rc;
282                 }
283                 rc = rmdir(dest);
284                 if (rc) {
285                         ERROR("in move_real_workdir, can't remove dir %s", dest);
286                         return rc;
287                 }
288         } else {
289                 /* check parent of dest */
290                 iter = strrchr(dest, '/');
291                 len = iter ? iter - dest : 0;
292                 if (len) {
293                         /* is parent existing? */
294                         copy = strndupa(dest, len);
295                         rc = stat(copy, &s);
296                         if (!rc) {
297                                 /* found an entry */
298                                 if (!S_ISDIR(s.st_mode)) {
299                                         ERROR("in move_real_workdir, '%s' isn't a directory", copy);
300                                         errno = ENOTDIR;
301                                         return -1;
302                                 }
303                         } else if (!parents) {
304                                 /* parent entry not found but not allowed to create it */
305                                 ERROR("in move_real_workdir, parent directory '%s' not found: %m", copy);
306                                 return -1;
307                         } else {
308                                 /* parent entries to be created */
309                                 l = len;
310                                 for(;;) {
311                                         /* backward loop */
312                                         rc = mkdir(copy, mode);
313                                         if (!rc)
314                                                 break;
315                                         if (errno != ENOENT) {
316                                                 ERROR("in move_real_workdir, mkdir '%s' failed: %m", copy);
317                                                 return -1;
318                                         }
319                                         while (l && copy[l] != '/')
320                                                 l--;
321                                         if (l == 0) {
322                                                 ERROR("in move_real_workdir, internal error");
323                                                 errno = EINVAL;
324                                                 return -1;
325                                         }
326                                         copy[l] = 0;
327                                 }
328                                 while(l < len) {
329                                         /* forward loop */
330                                         copy[l] = '/';
331                                         while (copy[++l]);
332                                         rc = mkdir(copy, mode);
333                                         if (rc && errno != EEXIST) {
334                                                 ERROR("in move_real_workdir, mkdir '%s' failed: %m", copy);
335                                                 return -1;
336                                         }
337                                 }
338                         }
339                 }
340         }
341
342         /* try to rename now */
343         close(workdirfd);
344         workdirfd = -1;
345         rc = renameat(AT_FDCWD, workdir, AT_FDCWD, dest);
346         if (rc) {
347                 ERROR("in move_real_workdir, renameat failed %s -> %s: %m", workdir, dest);
348                 return -1;
349         }
350
351         return set_real_workdir(dest, 0);
352 }
353
354 int move_workdir(const char *dest, int parents, int force)
355 {
356         char *rp;
357         int rc;
358
359         if (dest[0] == '/')
360                 return move_real_workdir(dest, parents, force);
361
362         rp = realpath(dest, NULL);
363         if (!rp) {
364                 ERROR("realpath failed for %s", dest);
365                 return -1;
366         }
367         rc = move_real_workdir(rp, parents, force);
368         free(rp);
369         return rc;
370 }
371