]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/machine-image.c
tmpfiles.d: upgrade a couple of directories we create at boot to subvolumes
[thirdparty/systemd.git] / src / shared / machine-image.c
CommitLineData
cd61c3bf
LP
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>
ebd93cb6 23#include <fcntl.h>
cd61c3bf
LP
24
25#include "strv.h"
26#include "utf8.h"
27#include "btrfs-util.h"
ebeccf9e 28#include "path-util.h"
ebd93cb6 29#include "copy.h"
003dffde 30#include "machine-image.h"
cd61c3bf 31
c2ce6a3d 32static const char image_search_path[] =
42c6f2c9 33 "/var/lib/machines\0"
c2ce6a3d 34 "/var/lib/container\0"
42c6f2c9
LP
35 "/usr/local/lib/machines\0"
36 "/usr/lib/machines\0";
c2ce6a3d 37
cd61c3bf
LP
38Image *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
c2ce6a3d 48static int image_new(
cd61c3bf 49 ImageType t,
5fc7f358 50 const char *pretty,
cd61c3bf 51 const char *path,
5fc7f358 52 const char *filename,
cd61c3bf 53 bool read_only,
10f9c755 54 usec_t crtime,
cd61c3bf 55 usec_t mtime,
c2ce6a3d 56 Image **ret) {
cd61c3bf
LP
57
58 _cleanup_(image_unrefp) Image *i = NULL;
cd61c3bf 59
cd61c3bf
LP
60 assert(t >= 0);
61 assert(t < _IMAGE_TYPE_MAX);
5fc7f358
LP
62 assert(pretty);
63 assert(filename);
c2ce6a3d 64 assert(ret);
cd61c3bf 65
c2ce6a3d 66 i = new0(Image, 1);
cd61c3bf
LP
67 if (!i)
68 return -ENOMEM;
69
70 i->type = t;
71 i->read_only = read_only;
10f9c755 72 i->crtime = crtime;
cd61c3bf 73 i->mtime = mtime;
cd61c3bf 74
5fc7f358 75 i->name = strdup(pretty);
cd61c3bf
LP
76 if (!i->name)
77 return -ENOMEM;
78
5fc7f358
LP
79 if (path)
80 i->path = strjoin(path, "/", filename, NULL);
81 else
82 i->path = strdup(filename);
ebeccf9e 83
5fc7f358
LP
84 if (!i->path)
85 return -ENOMEM;
86
87 path_kill_slashes(i->path);
cd61c3bf 88
c2ce6a3d 89 *ret = i;
cd61c3bf 90 i = NULL;
c2ce6a3d 91
cd61c3bf
LP
92 return 0;
93}
94
5fc7f358
LP
95static int image_make(
96 const char *pretty,
97 int dfd,
98 const char *path,
99 const char *filename,
100 Image **ret) {
101
c2ce6a3d 102 struct stat st;
5fc7f358 103 bool read_only;
cd61c3bf
LP
104 int r;
105
5fc7f358 106 assert(filename);
cd61c3bf 107
c2ce6a3d
LP
108 /* We explicitly *do* follow symlinks here, since we want to
109 * allow symlinking trees into /var/lib/container/, and treat
110 * them normally. */
cd61c3bf 111
5fc7f358 112 if (fstatat(dfd, filename, &st, 0) < 0)
c2ce6a3d 113 return -errno;
cd61c3bf 114
5fc7f358
LP
115 read_only =
116 (path && path_startswith(path, "/usr")) ||
08ff5529 117 (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS);
86e339c8 118
c2ce6a3d 119 if (S_ISDIR(st.st_mode)) {
cd61c3bf 120
c2ce6a3d
LP
121 if (!ret)
122 return 1;
cd61c3bf 123
5fc7f358
LP
124 if (!pretty)
125 pretty = filename;
126
c2ce6a3d
LP
127 /* btrfs subvolumes have inode 256 */
128 if (st.st_ino == 256) {
129 _cleanup_close_ int fd = -1;
130 struct statfs sfs;
cd61c3bf 131
5fc7f358 132 fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
c2ce6a3d
LP
133 if (fd < 0)
134 return -errno;
cd61c3bf 135
c2ce6a3d
LP
136 if (fstatfs(fd, &sfs) < 0)
137 return -errno;
cd61c3bf 138
c2ce6a3d 139 if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) {
10f9c755 140 BtrfsSubvolInfo info;
cd61c3bf 141
c2ce6a3d 142 /* It's a btrfs subvolume */
cd61c3bf 143
10f9c755
LP
144 r = btrfs_subvol_get_info_fd(fd, &info);
145 if (r < 0)
146 return r;
c2ce6a3d
LP
147
148 r = image_new(IMAGE_SUBVOLUME,
5fc7f358 149 pretty,
c2ce6a3d 150 path,
5fc7f358
LP
151 filename,
152 info.read_only || read_only,
10f9c755 153 info.otime,
c2ce6a3d 154 0,
c2ce6a3d
LP
155 ret);
156 if (r < 0)
157 return r;
158
159 return 1;
cd61c3bf 160 }
c2ce6a3d 161 }
cd61c3bf 162
c2ce6a3d 163 /* It's just a normal directory. */
cd61c3bf 164
c2ce6a3d 165 r = image_new(IMAGE_DIRECTORY,
5fc7f358 166 pretty,
c2ce6a3d 167 path,
5fc7f358
LP
168 filename,
169 read_only,
c2ce6a3d
LP
170 0,
171 0,
172 ret);
173 if (r < 0)
174 return r;
cd61c3bf 175
c2ce6a3d 176 return 1;
cd61c3bf 177
5fc7f358 178 } else if (S_ISREG(st.st_mode) && endswith(filename, ".gpt")) {
10f9c755 179 usec_t crtime = 0;
cd61c3bf 180
c2ce6a3d 181 /* It's a GPT block device */
cd61c3bf 182
c2ce6a3d
LP
183 if (!ret)
184 return 1;
cd61c3bf 185
5fc7f358 186 fd_getcrtime_at(dfd, filename, &crtime, 0);
10f9c755 187
5fc7f358
LP
188 if (!pretty)
189 pretty = strndupa(filename, strlen(filename) - 4);
10f9c755 190
c2ce6a3d 191 r = image_new(IMAGE_GPT,
5fc7f358 192 pretty,
c2ce6a3d 193 path,
5fc7f358
LP
194 filename,
195 !(st.st_mode & 0222) || read_only,
10f9c755 196 crtime,
c2ce6a3d 197 timespec_load(&st.st_mtim),
c2ce6a3d
LP
198 ret);
199 if (r < 0)
200 return r;
cd61c3bf 201
c2ce6a3d
LP
202 return 1;
203 }
cd61c3bf 204
c2ce6a3d
LP
205 return 0;
206}
cd61c3bf 207
c2ce6a3d
LP
208int image_find(const char *name, Image **ret) {
209 const char *path;
210 int r;
cd61c3bf 211
c2ce6a3d 212 assert(name);
cd61c3bf 213
c2ce6a3d
LP
214 /* There are no images with invalid names */
215 if (!image_name_is_valid(name))
216 return 0;
cd61c3bf 217
c2ce6a3d
LP
218 NULSTR_FOREACH(path, image_search_path) {
219 _cleanup_closedir_ DIR *d = NULL;
cd61c3bf 220
c2ce6a3d
LP
221 d = opendir(path);
222 if (!d) {
223 if (errno == ENOENT)
224 continue;
cd61c3bf 225
c2ce6a3d
LP
226 return -errno;
227 }
cd61c3bf 228
5fc7f358
LP
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 }
c2ce6a3d
LP
241 if (r < 0)
242 return r;
cd61c3bf 243
c2ce6a3d
LP
244 return 1;
245 }
246
5fc7f358 247 if (streq(name, ".host"))
27c88c4e 248 return image_make(".host", AT_FDCWD, NULL, "/", ret);
5fc7f358 249
c2ce6a3d
LP
250 return 0;
251};
252
253int 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)
a67a4c8c 266 continue;
c2ce6a3d
LP
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
5fc7f358 280 r = image_make(NULL, dirfd(d), path, de->d_name, &image);
c2ce6a3d
LP
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;
cd61c3bf
LP
291 }
292 }
293
5fc7f358
LP
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
cd61c3bf
LP
309 return 0;
310}
311
312void 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
08682124 321int image_remove(Image *i) {
08682124
LP
322 assert(i);
323
324 if (path_equal(i->path, "/") ||
325 path_startswith(i->path, "/usr"))
326 return -EROFS;
327
ebd93cb6
LP
328 switch (i->type) {
329
330 case IMAGE_SUBVOLUME:
08682124 331 return btrfs_subvol_remove(i->path);
ebd93cb6
LP
332
333 case IMAGE_DIRECTORY:
334 case IMAGE_GPT:
08682124 335 return rm_rf_dangerous(i->path, false, true, false);
ebd93cb6
LP
336
337 default:
338 return -ENOTSUP;
339 }
340}
341
342int 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
401int 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
441int 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;
08682124
LP
474}
475
cd61c3bf
LP
476static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
477 [IMAGE_DIRECTORY] = "directory",
478 [IMAGE_SUBVOLUME] = "subvolume",
479 [IMAGE_GPT] = "gpt",
480};
481
482DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);