1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2013 Lennart Poettering
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.
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.
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/>.
22 #include <sys/statfs.h>
27 #include "btrfs-util.h"
28 #include "path-util.h"
30 #include "machine-image.h"
32 static const char image_search_path[] =
34 "/var/lib/container\0"
35 "/usr/local/lib/machines\0"
36 "/usr/lib/machines\0";
38 Image *image_unref(Image *i) {
58 _cleanup_(image_unrefp) Image *i = NULL;
61 assert(t < _IMAGE_TYPE_MAX);
71 i->read_only = read_only;
74 i->size = i->size_exclusive = (uint64_t) -1;
75 i->limit = i->limit_exclusive = (uint64_t) -1;
77 i->name = strdup(pretty);
82 i->path = strjoin(path, "/", filename, NULL);
84 i->path = strdup(filename);
89 path_kill_slashes(i->path);
97 static int image_make(
101 const char *filename,
110 /* We explicitly *do* follow symlinks here, since we want to
111 * allow symlinking trees into /var/lib/container/, and treat
114 if (fstatat(dfd, filename, &st, 0) < 0)
118 (path && path_startswith(path, "/usr")) ||
119 (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS);
121 if (S_ISDIR(st.st_mode)) {
129 /* btrfs subvolumes have inode 256 */
130 if (st.st_ino == 256) {
131 _cleanup_close_ int fd = -1;
134 fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
138 if (fstatfs(fd, &sfs) < 0)
141 if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) {
142 BtrfsSubvolInfo info;
143 BtrfsQuotaInfo quota;
145 /* It's a btrfs subvolume */
147 r = btrfs_subvol_get_info_fd(fd, &info);
151 r = image_new(IMAGE_SUBVOLUME,
155 info.read_only || read_only,
162 r = btrfs_subvol_get_quota_fd(fd, "a);
164 (*ret)->size = quota.referred;
165 (*ret)->size_exclusive = quota.exclusive;
167 (*ret)->limit = quota.referred_max;
168 (*ret)->limit_exclusive = quota.exclusive_max;
175 /* It's just a normal directory. */
177 r = image_new(IMAGE_DIRECTORY,
190 } else if (S_ISREG(st.st_mode) && endswith(filename, ".gpt")) {
193 /* It's a GPT block device */
198 fd_getcrtime_at(dfd, filename, &crtime, 0);
201 pretty = strndupa(filename, strlen(filename) - 4);
203 r = image_new(IMAGE_GPT,
207 !(st.st_mode & 0222) || read_only,
209 timespec_load(&st.st_mtim),
214 (*ret)->size = (*ret)->size_exclusive = st.st_blocks * 512;
215 (*ret)->limit = (*ret)->limit_exclusive = st.st_size;
223 int image_find(const char *name, Image **ret) {
229 /* There are no images with invalid names */
230 if (!image_name_is_valid(name))
233 NULSTR_FOREACH(path, image_search_path) {
234 _cleanup_closedir_ DIR *d = NULL;
244 r = image_make(NULL, dirfd(d), path, name, ret);
245 if (r == 0 || r == -ENOENT) {
246 _cleanup_free_ char *gpt = NULL;
248 gpt = strappend(name, ".gpt");
252 r = image_make(NULL, dirfd(d), path, gpt, ret);
253 if (r == 0 || r == -ENOENT)
262 if (streq(name, ".host"))
263 return image_make(".host", AT_FDCWD, NULL, "/", ret);
268 int image_discover(Hashmap *h) {
274 NULSTR_FOREACH(path, image_search_path) {
275 _cleanup_closedir_ DIR *d = NULL;
286 FOREACH_DIRENT_ALL(de, d, return -errno) {
287 _cleanup_(image_unrefp) Image *image = NULL;
289 if (!image_name_is_valid(de->d_name))
292 if (hashmap_contains(h, de->d_name))
295 r = image_make(NULL, dirfd(d), path, de->d_name, &image);
296 if (r == 0 || r == -ENOENT)
301 r = hashmap_put(h, image->name, image);
309 if (!hashmap_contains(h, ".host")) {
310 _cleanup_(image_unrefp) Image *image = NULL;
312 r = image_make(".host", AT_FDCWD, NULL, "/", &image);
316 r = hashmap_put(h, image->name, image);
327 void image_hashmap_free(Hashmap *map) {
330 while ((i = hashmap_steal_first(map)))
336 int image_remove(Image *i) {
339 if (path_equal(i->path, "/") ||
340 path_startswith(i->path, "/usr"))
345 case IMAGE_SUBVOLUME:
346 return btrfs_subvol_remove(i->path);
348 case IMAGE_DIRECTORY:
350 return rm_rf_dangerous(i->path, false, true, false);
357 int image_rename(Image *i, const char *new_name) {
358 _cleanup_free_ char *new_path = NULL, *nn = NULL;
363 if (!image_name_is_valid(new_name))
366 if (path_equal(i->path, "/") ||
367 path_startswith(i->path, "/usr"))
370 r = image_find(new_name, NULL);
378 case IMAGE_SUBVOLUME:
379 case IMAGE_DIRECTORY:
380 new_path = file_in_same_dir(i->path, new_name);
386 fn = strappenda(new_name, ".gpt");
387 new_path = file_in_same_dir(i->path, fn);
398 nn = strdup(new_name);
402 if (renameat2(AT_FDCWD, i->path, AT_FDCWD, new_path, RENAME_NOREPLACE) < 0)
416 int image_clone(Image *i, const char *new_name, bool read_only) {
417 const char *new_path;
422 if (!image_name_is_valid(new_name))
425 r = image_find(new_name, NULL);
433 case IMAGE_SUBVOLUME:
434 case IMAGE_DIRECTORY:
435 new_path = strappenda("/var/lib/container/", new_name);
437 r = btrfs_subvol_snapshot(i->path, new_path, read_only, true);
441 new_path = strappenda("/var/lib/container/", new_name, ".gpt");
443 r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, false);
456 int image_read_only(Image *i, bool b) {
460 if (path_equal(i->path, "/") ||
461 path_startswith(i->path, "/usr"))
466 case IMAGE_SUBVOLUME:
467 r = btrfs_subvol_set_read_only(i->path, b);
475 if (stat(i->path, &st) < 0)
478 if (chmod(i->path, (st.st_mode & 0444) | (b ? 0000 : 0200)) < 0)
483 case IMAGE_DIRECTORY:
491 static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
492 [IMAGE_DIRECTORY] = "directory",
493 [IMAGE_SUBVOLUME] = "subvolume",
497 DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);