chiark / gitweb /
7c041fab7624315d3a7e31b39c73fab7fd5f67ea
[elogind.git] / src / shared / machine-image.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <sys/statfs.h>
23 #include <fcntl.h>
24
25 #include "strv.h"
26 #include "utf8.h"
27 #include "btrfs-util.h"
28 #include "path-util.h"
29 #include "copy.h"
30 #include "machine-image.h"
31
32 static const char image_search_path[] =
33         "/var/lib/machines\0"
34         "/var/lib/container\0"
35         "/usr/local/lib/machines\0"
36         "/usr/lib/machines\0";
37
38 Image *image_unref(Image *i) {
39         if (!i)
40                 return NULL;
41
42         free(i->name);
43         free(i->path);
44         free(i);
45         return NULL;
46 }
47
48 static int image_new(
49                 ImageType t,
50                 const char *pretty,
51                 const char *path,
52                 const char *filename,
53                 bool read_only,
54                 usec_t crtime,
55                 usec_t mtime,
56                 Image **ret) {
57
58         _cleanup_(image_unrefp) Image *i = NULL;
59
60         assert(t >= 0);
61         assert(t < _IMAGE_TYPE_MAX);
62         assert(pretty);
63         assert(filename);
64         assert(ret);
65
66         i = new0(Image, 1);
67         if (!i)
68                 return -ENOMEM;
69
70         i->type = t;
71         i->read_only = read_only;
72         i->crtime = crtime;
73         i->mtime = mtime;
74
75         i->name = strdup(pretty);
76         if (!i->name)
77                 return -ENOMEM;
78
79         if (path)
80                 i->path = strjoin(path, "/", filename, NULL);
81         else
82                 i->path = strdup(filename);
83
84         if (!i->path)
85                 return -ENOMEM;
86
87         path_kill_slashes(i->path);
88
89         *ret = i;
90         i = NULL;
91
92         return 0;
93 }
94
95 static int image_make(
96                 const char *pretty,
97                 int dfd,
98                 const char *path,
99                 const char *filename,
100                 Image **ret) {
101
102         struct stat st;
103         bool read_only;
104         int r;
105
106         assert(filename);
107
108         /* We explicitly *do* follow symlinks here, since we want to
109          * allow symlinking trees into /var/lib/container/, and treat
110          * them normally. */
111
112         if (fstatat(dfd, filename, &st, 0) < 0)
113                 return -errno;
114
115         read_only =
116                 (path && path_startswith(path, "/usr")) ||
117                 (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS);
118
119         if (S_ISDIR(st.st_mode)) {
120
121                 if (!ret)
122                         return 1;
123
124                 if (!pretty)
125                         pretty = filename;
126
127                 /* btrfs subvolumes have inode 256 */
128                 if (st.st_ino == 256) {
129                         _cleanup_close_ int fd = -1;
130                         struct statfs sfs;
131
132                         fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
133                         if (fd < 0)
134                                 return -errno;
135
136                         if (fstatfs(fd, &sfs) < 0)
137                                 return -errno;
138
139                         if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) {
140                                 BtrfsSubvolInfo info;
141
142                                 /* It's a btrfs subvolume */
143
144                                 r = btrfs_subvol_get_info_fd(fd, &info);
145                                 if (r < 0)
146                                         return r;
147
148                                 r = image_new(IMAGE_SUBVOLUME,
149                                               pretty,
150                                               path,
151                                               filename,
152                                               info.read_only || read_only,
153                                               info.otime,
154                                               0,
155                                               ret);
156                                 if (r < 0)
157                                         return r;
158
159                                 return 1;
160                         }
161                 }
162
163                 /* It's just a normal directory. */
164
165                 r = image_new(IMAGE_DIRECTORY,
166                               pretty,
167                               path,
168                               filename,
169                               read_only,
170                               0,
171                               0,
172                               ret);
173                 if (r < 0)
174                         return r;
175
176                 return 1;
177
178         } else if (S_ISREG(st.st_mode) && endswith(filename, ".gpt")) {
179                 usec_t crtime = 0;
180
181                 /* It's a GPT block device */
182
183                 if (!ret)
184                         return 1;
185
186                 fd_getcrtime_at(dfd, filename, &crtime, 0);
187
188                 if (!pretty)
189                         pretty = strndupa(filename, strlen(filename) - 4);
190
191                 r = image_new(IMAGE_GPT,
192                               pretty,
193                               path,
194                               filename,
195                               !(st.st_mode & 0222) || read_only,
196                               crtime,
197                               timespec_load(&st.st_mtim),
198                               ret);
199                 if (r < 0)
200                         return r;
201
202                 return 1;
203         }
204
205         return 0;
206 }
207
208 int image_find(const char *name, Image **ret) {
209         const char *path;
210         int r;
211
212         assert(name);
213
214         /* There are no images with invalid names */
215         if (!image_name_is_valid(name))
216                 return 0;
217
218         NULSTR_FOREACH(path, image_search_path) {
219                 _cleanup_closedir_ DIR *d = NULL;
220
221                 d = opendir(path);
222                 if (!d) {
223                         if (errno == ENOENT)
224                                 continue;
225
226                         return -errno;
227                 }
228
229                 r = image_make(NULL, dirfd(d), path, name, ret);
230                 if (r == 0 || r == -ENOENT) {
231                         _cleanup_free_ char *gpt = NULL;
232
233                         gpt = strappend(name, ".gpt");
234                         if (!gpt)
235                                 return -ENOMEM;
236
237                         r = image_make(NULL, dirfd(d), path, gpt, ret);
238                         if (r == 0 || r == -ENOENT)
239                                 continue;
240                 }
241                 if (r < 0)
242                         return r;
243
244                 return 1;
245         }
246
247         if (streq(name, ".host"))
248                 return image_make(".host", AT_FDCWD, NULL, "/", ret);
249
250         return 0;
251 };
252
253 int image_discover(Hashmap *h) {
254         const char *path;
255         int r;
256
257         assert(h);
258
259         NULSTR_FOREACH(path, image_search_path) {
260                 _cleanup_closedir_ DIR *d = NULL;
261                 struct dirent *de;
262
263                 d = opendir(path);
264                 if (!d) {
265                         if (errno == ENOENT)
266                                 continue;
267
268                         return -errno;
269                 }
270
271                 FOREACH_DIRENT_ALL(de, d, return -errno) {
272                         _cleanup_(image_unrefp) Image *image = NULL;
273
274                         if (!image_name_is_valid(de->d_name))
275                                 continue;
276
277                         if (hashmap_contains(h, de->d_name))
278                                 continue;
279
280                         r = image_make(NULL, dirfd(d), path, de->d_name, &image);
281                         if (r == 0 || r == -ENOENT)
282                                 continue;
283                         if (r < 0)
284                                 return r;
285
286                         r = hashmap_put(h, image->name, image);
287                         if (r < 0)
288                                 return r;
289
290                         image = NULL;
291                 }
292         }
293
294         if (!hashmap_contains(h, ".host")) {
295                 _cleanup_(image_unrefp) Image *image = NULL;
296
297                 r = image_make(".host", AT_FDCWD, NULL, "/", &image);
298                 if (r < 0)
299                         return r;
300
301                 r = hashmap_put(h, image->name, image);
302                 if (r < 0)
303                         return r;
304
305                 image = NULL;
306
307         }
308
309         return 0;
310 }
311
312 void image_hashmap_free(Hashmap *map) {
313         Image *i;
314
315         while ((i = hashmap_steal_first(map)))
316                 image_unref(i);
317
318         hashmap_free(map);
319 }
320
321 int image_remove(Image *i) {
322         assert(i);
323
324         if (path_equal(i->path, "/") ||
325             path_startswith(i->path, "/usr"))
326                 return -EROFS;
327
328         switch (i->type) {
329
330         case IMAGE_SUBVOLUME:
331                 return btrfs_subvol_remove(i->path);
332
333         case IMAGE_DIRECTORY:
334         case IMAGE_GPT:
335                 return rm_rf_dangerous(i->path, false, true, false);
336
337         default:
338                 return -ENOTSUP;
339         }
340 }
341
342 int image_rename(Image *i, const char *new_name) {
343         _cleanup_free_ char *new_path = NULL, *nn = NULL;
344         int r;
345
346         assert(i);
347
348         if (!image_name_is_valid(new_name))
349                 return -EINVAL;
350
351         if (path_equal(i->path, "/") ||
352             path_startswith(i->path, "/usr"))
353                 return -EROFS;
354
355         r = image_find(new_name, NULL);
356         if (r < 0)
357                 return r;
358         if (r > 0)
359                 return -EEXIST;
360
361         switch (i->type) {
362
363         case IMAGE_SUBVOLUME:
364         case IMAGE_DIRECTORY:
365                 new_path = file_in_same_dir(i->path, new_name);
366                 break;
367
368         case IMAGE_GPT: {
369                 const char *fn;
370
371                 fn = strappenda(new_name, ".gpt");
372                 new_path = file_in_same_dir(i->path, fn);
373                 break;
374         }
375
376         default:
377                 return -ENOTSUP;
378         }
379
380         if (!new_path)
381                 return -ENOMEM;
382
383         nn = strdup(new_name);
384         if (!nn)
385                 return -ENOMEM;
386
387         if (renameat2(AT_FDCWD, i->path, AT_FDCWD, new_path, RENAME_NOREPLACE) < 0)
388                 return -errno;
389
390         free(i->path);
391         i->path = new_path;
392         new_path = NULL;
393
394         free(i->name);
395         i->name = nn;
396         nn = NULL;
397
398         return 0;
399 }
400
401 int image_clone(Image *i, const char *new_name, bool read_only) {
402         const char *new_path;
403         int r;
404
405         assert(i);
406
407         if (!image_name_is_valid(new_name))
408                 return -EINVAL;
409
410         r = image_find(new_name, NULL);
411         if (r < 0)
412                 return r;
413         if (r > 0)
414                 return -EEXIST;
415
416         switch (i->type) {
417
418         case IMAGE_SUBVOLUME:
419         case IMAGE_DIRECTORY:
420                 new_path = strappenda("/var/lib/container/", new_name);
421
422                 r = btrfs_subvol_snapshot(i->path, new_path, read_only, true);
423                 break;
424
425         case IMAGE_GPT:
426                 new_path = strappenda("/var/lib/container/", new_name, ".gpt");
427
428                 r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, false);
429                 break;
430
431         default:
432                 return -ENOTSUP;
433         }
434
435         if (r < 0)
436                 return r;
437
438         return 0;
439 }
440
441 int image_read_only(Image *i, bool b) {
442         int r;
443         assert(i);
444
445         if (path_equal(i->path, "/") ||
446             path_startswith(i->path, "/usr"))
447                 return -EROFS;
448
449         switch (i->type) {
450
451         case IMAGE_SUBVOLUME:
452                 r = btrfs_subvol_set_read_only(i->path, b);
453                 if (r < 0)
454                         return r;
455                 break;
456
457         case IMAGE_GPT: {
458                 struct stat st;
459
460                 if (stat(i->path, &st) < 0)
461                         return -errno;
462
463                 if (chmod(i->path, (st.st_mode & 0444) | (b ? 0000 : 0200)) < 0)
464                         return -errno;
465                 break;
466         }
467
468         case IMAGE_DIRECTORY:
469         default:
470                 return -ENOTSUP;
471         }
472
473         return 0;
474 }
475
476 static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
477         [IMAGE_DIRECTORY] = "directory",
478         [IMAGE_SUBVOLUME] = "subvolume",
479         [IMAGE_GPT] = "gpt",
480 };
481
482 DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);