chiark / gitweb /
36b64e1fab8fec9cf0f6db88157f78c1509cd52a
[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         i->size = i->size_exclusive = (uint64_t) -1;
75         i->limit = i->limit_exclusive = (uint64_t) -1;
76
77         i->name = strdup(pretty);
78         if (!i->name)
79                 return -ENOMEM;
80
81         if (path)
82                 i->path = strjoin(path, "/", filename, NULL);
83         else
84                 i->path = strdup(filename);
85
86         if (!i->path)
87                 return -ENOMEM;
88
89         path_kill_slashes(i->path);
90
91         *ret = i;
92         i = NULL;
93
94         return 0;
95 }
96
97 static int image_make(
98                 const char *pretty,
99                 int dfd,
100                 const char *path,
101                 const char *filename,
102                 Image **ret) {
103
104         struct stat st;
105         bool read_only;
106         int r;
107
108         assert(filename);
109
110         /* We explicitly *do* follow symlinks here, since we want to
111          * allow symlinking trees into /var/lib/container/, and treat
112          * them normally. */
113
114         if (fstatat(dfd, filename, &st, 0) < 0)
115                 return -errno;
116
117         read_only =
118                 (path && path_startswith(path, "/usr")) ||
119                 (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS);
120
121         if (S_ISDIR(st.st_mode)) {
122
123                 if (!ret)
124                         return 1;
125
126                 if (!pretty)
127                         pretty = filename;
128
129                 /* btrfs subvolumes have inode 256 */
130                 if (st.st_ino == 256) {
131                         _cleanup_close_ int fd = -1;
132                         struct statfs sfs;
133
134                         fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
135                         if (fd < 0)
136                                 return -errno;
137
138                         if (fstatfs(fd, &sfs) < 0)
139                                 return -errno;
140
141                         if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) {
142                                 BtrfsSubvolInfo info;
143                                 BtrfsQuotaInfo quota;
144
145                                 /* It's a btrfs subvolume */
146
147                                 r = btrfs_subvol_get_info_fd(fd, &info);
148                                 if (r < 0)
149                                         return r;
150
151                                 r = image_new(IMAGE_SUBVOLUME,
152                                               pretty,
153                                               path,
154                                               filename,
155                                               info.read_only || read_only,
156                                               info.otime,
157                                               0,
158                                               ret);
159                                 if (r < 0)
160                                         return r;
161
162                                 r = btrfs_subvol_get_quota_fd(fd, &quota);
163                                 if (r >= 0) {
164                                         (*ret)->size = quota.referred;
165                                         (*ret)->size_exclusive = quota.exclusive;
166
167                                         (*ret)->limit = quota.referred_max;
168                                         (*ret)->limit_exclusive = quota.exclusive_max;
169                                 }
170
171                                 return 1;
172                         }
173                 }
174
175                 /* It's just a normal directory. */
176
177                 r = image_new(IMAGE_DIRECTORY,
178                               pretty,
179                               path,
180                               filename,
181                               read_only,
182                               0,
183                               0,
184                               ret);
185                 if (r < 0)
186                         return r;
187
188                 return 1;
189
190         } else if (S_ISREG(st.st_mode) && endswith(filename, ".gpt")) {
191                 usec_t crtime = 0;
192
193                 /* It's a GPT block device */
194
195                 if (!ret)
196                         return 1;
197
198                 fd_getcrtime_at(dfd, filename, &crtime, 0);
199
200                 if (!pretty)
201                         pretty = strndupa(filename, strlen(filename) - 4);
202
203                 r = image_new(IMAGE_GPT,
204                               pretty,
205                               path,
206                               filename,
207                               !(st.st_mode & 0222) || read_only,
208                               crtime,
209                               timespec_load(&st.st_mtim),
210                               ret);
211                 if (r < 0)
212                         return r;
213
214                 (*ret)->size = (*ret)->size_exclusive = st.st_blocks * 512;
215                 (*ret)->limit = (*ret)->limit_exclusive = st.st_size;
216
217                 return 1;
218         }
219
220         return 0;
221 }
222
223 int image_find(const char *name, Image **ret) {
224         const char *path;
225         int r;
226
227         assert(name);
228
229         /* There are no images with invalid names */
230         if (!image_name_is_valid(name))
231                 return 0;
232
233         NULSTR_FOREACH(path, image_search_path) {
234                 _cleanup_closedir_ DIR *d = NULL;
235
236                 d = opendir(path);
237                 if (!d) {
238                         if (errno == ENOENT)
239                                 continue;
240
241                         return -errno;
242                 }
243
244                 r = image_make(NULL, dirfd(d), path, name, ret);
245                 if (r == 0 || r == -ENOENT) {
246                         _cleanup_free_ char *gpt = NULL;
247
248                         gpt = strappend(name, ".gpt");
249                         if (!gpt)
250                                 return -ENOMEM;
251
252                         r = image_make(NULL, dirfd(d), path, gpt, ret);
253                         if (r == 0 || r == -ENOENT)
254                                 continue;
255                 }
256                 if (r < 0)
257                         return r;
258
259                 return 1;
260         }
261
262         if (streq(name, ".host"))
263                 return image_make(".host", AT_FDCWD, NULL, "/", ret);
264
265         return 0;
266 };
267
268 int image_discover(Hashmap *h) {
269         const char *path;
270         int r;
271
272         assert(h);
273
274         NULSTR_FOREACH(path, image_search_path) {
275                 _cleanup_closedir_ DIR *d = NULL;
276                 struct dirent *de;
277
278                 d = opendir(path);
279                 if (!d) {
280                         if (errno == ENOENT)
281                                 continue;
282
283                         return -errno;
284                 }
285
286                 FOREACH_DIRENT_ALL(de, d, return -errno) {
287                         _cleanup_(image_unrefp) Image *image = NULL;
288
289                         if (!image_name_is_valid(de->d_name))
290                                 continue;
291
292                         if (hashmap_contains(h, de->d_name))
293                                 continue;
294
295                         r = image_make(NULL, dirfd(d), path, de->d_name, &image);
296                         if (r == 0 || r == -ENOENT)
297                                 continue;
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         if (!hashmap_contains(h, ".host")) {
310                 _cleanup_(image_unrefp) Image *image = NULL;
311
312                 r = image_make(".host", AT_FDCWD, NULL, "/", &image);
313                 if (r < 0)
314                         return r;
315
316                 r = hashmap_put(h, image->name, image);
317                 if (r < 0)
318                         return r;
319
320                 image = NULL;
321
322         }
323
324         return 0;
325 }
326
327 void image_hashmap_free(Hashmap *map) {
328         Image *i;
329
330         while ((i = hashmap_steal_first(map)))
331                 image_unref(i);
332
333         hashmap_free(map);
334 }
335
336 int image_remove(Image *i) {
337         assert(i);
338
339         if (path_equal(i->path, "/") ||
340             path_startswith(i->path, "/usr"))
341                 return -EROFS;
342
343         switch (i->type) {
344
345         case IMAGE_SUBVOLUME:
346                 return btrfs_subvol_remove(i->path);
347
348         case IMAGE_DIRECTORY:
349         case IMAGE_GPT:
350                 return rm_rf_dangerous(i->path, false, true, false);
351
352         default:
353                 return -ENOTSUP;
354         }
355 }
356
357 int image_rename(Image *i, const char *new_name) {
358         _cleanup_free_ char *new_path = NULL, *nn = NULL;
359         int r;
360
361         assert(i);
362
363         if (!image_name_is_valid(new_name))
364                 return -EINVAL;
365
366         if (path_equal(i->path, "/") ||
367             path_startswith(i->path, "/usr"))
368                 return -EROFS;
369
370         r = image_find(new_name, NULL);
371         if (r < 0)
372                 return r;
373         if (r > 0)
374                 return -EEXIST;
375
376         switch (i->type) {
377
378         case IMAGE_SUBVOLUME:
379         case IMAGE_DIRECTORY:
380                 new_path = file_in_same_dir(i->path, new_name);
381                 break;
382
383         case IMAGE_GPT: {
384                 const char *fn;
385
386                 fn = strappenda(new_name, ".gpt");
387                 new_path = file_in_same_dir(i->path, fn);
388                 break;
389         }
390
391         default:
392                 return -ENOTSUP;
393         }
394
395         if (!new_path)
396                 return -ENOMEM;
397
398         nn = strdup(new_name);
399         if (!nn)
400                 return -ENOMEM;
401
402         if (renameat2(AT_FDCWD, i->path, AT_FDCWD, new_path, RENAME_NOREPLACE) < 0)
403                 return -errno;
404
405         free(i->path);
406         i->path = new_path;
407         new_path = NULL;
408
409         free(i->name);
410         i->name = nn;
411         nn = NULL;
412
413         return 0;
414 }
415
416 int image_clone(Image *i, const char *new_name, bool read_only) {
417         const char *new_path;
418         int r;
419
420         assert(i);
421
422         if (!image_name_is_valid(new_name))
423                 return -EINVAL;
424
425         r = image_find(new_name, NULL);
426         if (r < 0)
427                 return r;
428         if (r > 0)
429                 return -EEXIST;
430
431         switch (i->type) {
432
433         case IMAGE_SUBVOLUME:
434         case IMAGE_DIRECTORY:
435                 new_path = strappenda("/var/lib/container/", new_name);
436
437                 r = btrfs_subvol_snapshot(i->path, new_path, read_only, true);
438                 break;
439
440         case IMAGE_GPT:
441                 new_path = strappenda("/var/lib/container/", new_name, ".gpt");
442
443                 r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, false);
444                 break;
445
446         default:
447                 return -ENOTSUP;
448         }
449
450         if (r < 0)
451                 return r;
452
453         return 0;
454 }
455
456 int image_read_only(Image *i, bool b) {
457         int r;
458         assert(i);
459
460         if (path_equal(i->path, "/") ||
461             path_startswith(i->path, "/usr"))
462                 return -EROFS;
463
464         switch (i->type) {
465
466         case IMAGE_SUBVOLUME:
467                 r = btrfs_subvol_set_read_only(i->path, b);
468                 if (r < 0)
469                         return r;
470                 break;
471
472         case IMAGE_GPT: {
473                 struct stat st;
474
475                 if (stat(i->path, &st) < 0)
476                         return -errno;
477
478                 if (chmod(i->path, (st.st_mode & 0444) | (b ? 0000 : 0200)) < 0)
479                         return -errno;
480                 break;
481         }
482
483         case IMAGE_DIRECTORY:
484         default:
485                 return -ENOTSUP;
486         }
487
488         return 0;
489 }
490
491 static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
492         [IMAGE_DIRECTORY] = "directory",
493         [IMAGE_SUBVOLUME] = "subvolume",
494         [IMAGE_GPT] = "gpt",
495 };
496
497 DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);