]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/machine-image.c
tree-wide: use mfree more
[thirdparty/systemd.git] / src / shared / machine-image.c
index f166e9410d0f1385cdad959a92a92cb8687c2e3c..060f8d50c716b85615fe3b46b806957f392d7922 100644 (file)
@@ -1,5 +1,3 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
 /***
   This file is part of systemd.
 
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include <sys/statfs.h>
-#include <linux/fs.h>
+#include <dirent.h>
+#include <errno.h>
 #include <fcntl.h>
-
-#include "utf8.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <linux/fs.h>
+#include "alloc-util.h"
 #include "btrfs-util.h"
-#include "path-util.h"
+#include "chattr-util.h"
 #include "copy.h"
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "hashmap.h"
+#include "lockfile-util.h"
+#include "log.h"
+#include "macro.h"
+#include "machine-image.h"
 #include "mkdir.h"
+#include "path-util.h"
 #include "rm-rf.h"
-#include "machine-image.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "time-util.h"
+#include "utf8.h"
+#include "util.h"
+#include "xattr-util.h"
 
 static const char image_search_path[] =
         "/var/lib/machines\0"
-        "/var/lib/container\0"
+        "/var/lib/container\0" /* legacy */
         "/usr/local/lib/machines\0"
         "/usr/lib/machines\0";
 
@@ -43,8 +62,39 @@ Image *image_unref(Image *i) {
 
         free(i->name);
         free(i->path);
-        free(i);
-        return NULL;
+        return mfree(i);
+}
+
+static char **image_settings_path(Image *image) {
+        _cleanup_strv_free_ char **l = NULL;
+        char **ret;
+        const char *fn, *s;
+        unsigned i = 0;
+
+        assert(image);
+
+        l = new0(char*, 4);
+        if (!l)
+                return NULL;
+
+        fn = strjoina(image->name, ".nspawn");
+
+        FOREACH_STRING(s, "/etc/systemd/nspawn/", "/run/systemd/nspawn/") {
+                l[i] = strappend(s, fn);
+                if (!l[i])
+                        return NULL;
+
+                i++;
+        }
+
+        l[i] = file_in_same_dir(image->path, fn);
+        if (!l[i])
+                return NULL;
+
+        ret = l;
+        l = NULL;
+
+        return ret;
 }
 
 static int image_new(
@@ -136,18 +186,16 @@ static int image_make(
 
                 /* btrfs subvolumes have inode 256 */
                 if (st.st_ino == 256) {
-                        struct statfs sfs;
 
-                        if (fstatfs(fd, &sfs) < 0)
-                                return -errno;
-
-                        if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) {
+                        r = btrfs_is_filesystem(fd);
+                        if (r < 0)
+                                return r;
+                        if (r) {
                                 BtrfsSubvolInfo info;
-                                BtrfsQuotaInfo quota;
 
                                 /* It's a btrfs subvolume */
 
-                                r = btrfs_subvol_get_info_fd(fd, &info);
+                                r = btrfs_subvol_get_info_fd(fd, 0, &info);
                                 if (r < 0)
                                         return r;
 
@@ -162,13 +210,17 @@ static int image_make(
                                 if (r < 0)
                                         return r;
 
-                                r = btrfs_subvol_get_quota_fd(fd, &quota);
-                                if (r >= 0) {
-                                        (*ret)->usage = quota.referenced;
-                                        (*ret)->usage_exclusive = quota.exclusive;
+                                if (btrfs_quota_scan_ongoing(fd) == 0) {
+                                        BtrfsQuotaInfo quota;
+
+                                        r = btrfs_subvol_get_subtree_quota_fd(fd, 0, &quota);
+                                        if (r >= 0) {
+                                                (*ret)->usage = quota.referenced;
+                                                (*ret)->usage_exclusive = quota.exclusive;
 
-                                        (*ret)->limit = quota.referenced_max;
-                                        (*ret)->limit_exclusive = quota.exclusive_max;
+                                                (*ret)->limit = quota.referenced_max;
+                                                (*ret)->limit_exclusive = quota.exclusive_max;
+                                        }
                                 }
 
                                 return 1;
@@ -342,14 +394,19 @@ void image_hashmap_free(Hashmap *map) {
 
 int image_remove(Image *i) {
         _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
+        _cleanup_strv_free_ char **settings = NULL;
+        char **j;
         int r;
 
         assert(i);
 
-        if (path_equal(i->path, "/") ||
-            path_startswith(i->path, "/usr"))
+        if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i))
                 return -EROFS;
 
+        settings = image_settings_path(i);
+        if (!settings)
+                return -ENOMEM;
+
         /* Make sure we don't interfere with a running nspawn */
         r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock);
         if (r < 0)
@@ -358,28 +415,56 @@ int image_remove(Image *i) {
         switch (i->type) {
 
         case IMAGE_SUBVOLUME:
-                return btrfs_subvol_remove(i->path);
+                r = btrfs_subvol_remove(i->path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
+                if (r < 0)
+                        return r;
+                break;
 
         case IMAGE_DIRECTORY:
                 /* Allow deletion of read-only directories */
-                (void) chattr_path(i->path, false, FS_IMMUTABLE_FL);
-                return rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
+                (void) chattr_path(i->path, 0, FS_IMMUTABLE_FL);
+                r = rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
+                if (r < 0)
+                        return r;
+
+                break;
 
         case IMAGE_RAW:
                 if (unlink(i->path) < 0)
                         return -errno;
-
-                return 0;
+                break;
 
         default:
                 return -EOPNOTSUPP;
         }
+
+        STRV_FOREACH(j, settings) {
+                if (unlink(*j) < 0 && errno != ENOENT)
+                        log_debug_errno(errno, "Failed to unlink %s, ignoring: %m", *j);
+        }
+
+        return 0;
+}
+
+static int rename_settings_file(const char *path, const char *new_name) {
+        _cleanup_free_ char *rs = NULL;
+        const char *fn;
+
+        fn = strjoina(new_name, ".nspawn");
+
+        rs = file_in_same_dir(path, fn);
+        if (!rs)
+                return -ENOMEM;
+
+        return rename_noreplace(AT_FDCWD, path, AT_FDCWD, rs);
 }
 
 int image_rename(Image *i, const char *new_name) {
         _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT, name_lock = LOCK_FILE_INIT;
         _cleanup_free_ char *new_path = NULL, *nn = NULL;
+        _cleanup_strv_free_ char **settings = NULL;
         unsigned file_attr = 0;
+        char **j;
         int r;
 
         assert(i);
@@ -387,10 +472,13 @@ int image_rename(Image *i, const char *new_name) {
         if (!image_name_is_valid(new_name))
                 return -EINVAL;
 
-        if (path_equal(i->path, "/") ||
-            path_startswith(i->path, "/usr"))
+        if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i))
                 return -EROFS;
 
+        settings = image_settings_path(i);
+        if (!settings)
+                return -ENOMEM;
+
         /* Make sure we don't interfere with a running nspawn */
         r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock);
         if (r < 0)
@@ -398,7 +486,7 @@ int image_rename(Image *i, const char *new_name) {
 
         /* Make sure nobody takes the new name, between the time we
          * checked it is currently unused in all search paths, and the
-         * time we take possesion of it */
+         * time we take possession of it */
         r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock);
         if (r < 0)
                 return r;
@@ -416,7 +504,7 @@ int image_rename(Image *i, const char *new_name) {
                 (void) read_attr_path(i->path, &file_attr);
 
                 if (file_attr & FS_IMMUTABLE_FL)
-                        (void) chattr_path(i->path, false, FS_IMMUTABLE_FL);
+                        (void) chattr_path(i->path, 0, FS_IMMUTABLE_FL);
 
                 /* fall through */
 
@@ -449,7 +537,7 @@ int image_rename(Image *i, const char *new_name) {
 
         /* Restore the immutable bit, if it was set before */
         if (file_attr & FS_IMMUTABLE_FL)
-                (void) chattr_path(new_path, true, FS_IMMUTABLE_FL);
+                (void) chattr_path(new_path, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL);
 
         free(i->path);
         i->path = new_path;
@@ -459,12 +547,33 @@ int image_rename(Image *i, const char *new_name) {
         i->name = nn;
         nn = NULL;
 
+        STRV_FOREACH(j, settings) {
+                r = rename_settings_file(*j, new_name);
+                if (r < 0 && r != -ENOENT)
+                        log_debug_errno(r, "Failed to rename settings file %s, ignoring: %m", *j);
+        }
+
         return 0;
 }
 
+static int clone_settings_file(const char *path, const char *new_name) {
+        _cleanup_free_ char *rs = NULL;
+        const char *fn;
+
+        fn = strjoina(new_name, ".nspawn");
+
+        rs = file_in_same_dir(path, fn);
+        if (!rs)
+                return -ENOMEM;
+
+        return copy_file_atomic(path, rs, 0664, false, 0);
+}
+
 int image_clone(Image *i, const char *new_name, bool read_only) {
         _cleanup_release_lock_file_ LockFile name_lock = LOCK_FILE_INIT;
+        _cleanup_strv_free_ char **settings = NULL;
         const char *new_path;
+        char **j;
         int r;
 
         assert(i);
@@ -472,9 +581,13 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
         if (!image_name_is_valid(new_name))
                 return -EINVAL;
 
+        settings = image_settings_path(i);
+        if (!settings)
+                return -ENOMEM;
+
         /* Make sure nobody takes the new name, between the time we
          * checked it is currently unused in all search paths, and the
-         * time we take possesion of it */
+         * time we take possession of it */
         r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock);
         if (r < 0)
                 return r;
@@ -489,9 +602,22 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
 
         case IMAGE_SUBVOLUME:
         case IMAGE_DIRECTORY:
+                /* If we can we'll always try to create a new btrfs subvolume here, even if the source is a plain
+                 * directory.*/
+
                 new_path = strjoina("/var/lib/machines/", new_name);
 
-                r = btrfs_subvol_snapshot(i->path, new_path, read_only, true);
+                r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA);
+                if (r == -EOPNOTSUPP) {
+                        /* No btrfs snapshots supported, create a normal directory then. */
+
+                        r = copy_directory(i->path, new_path, false);
+                        if (r >= 0)
+                                (void) chattr_path(new_path, read_only ? FS_IMMUTABLE_FL : 0, FS_IMMUTABLE_FL);
+                } else if (r >= 0)
+                        /* Enable "subtree" quotas for the copy, if we didn't copy any quota from the source. */
+                        (void) btrfs_subvol_auto_qgroup(new_path, 0, true);
+
                 break;
 
         case IMAGE_RAW:
@@ -507,6 +633,12 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
         if (r < 0)
                 return r;
 
+        STRV_FOREACH(j, settings) {
+                r = clone_settings_file(*j, new_name);
+                if (r < 0 && r != -ENOENT)
+                        log_debug_errno(r, "Failed to clone settings %s, ignoring: %m", *j);
+        }
+
         return 0;
 }
 
@@ -515,8 +647,7 @@ int image_read_only(Image *i, bool b) {
         int r;
         assert(i);
 
-        if (path_equal(i->path, "/") ||
-            path_startswith(i->path, "/usr"))
+        if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i))
                 return -EROFS;
 
         /* Make sure we don't interfere with a running nspawn */
@@ -527,6 +658,10 @@ int image_read_only(Image *i, bool b) {
         switch (i->type) {
 
         case IMAGE_SUBVOLUME:
+
+                /* Note that we set the flag only on the top-level
+                 * subvolume of the image. */
+
                 r = btrfs_subvol_set_read_only(i->path, b);
                 if (r < 0)
                         return r;
@@ -542,7 +677,7 @@ int image_read_only(Image *i, bool b) {
                    a read-only subvolume, but at least something, and
                    we can read the value back.*/
 
-                r = chattr_path(i->path, b, FS_IMMUTABLE_FL);
+                r = chattr_path(i->path, b ? FS_IMMUTABLE_FL : 0, FS_IMMUTABLE_FL);
                 if (r < 0)
                         return r;
 
@@ -604,7 +739,7 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
                 return r;
 
         if (p) {
-                mkdir_p("/run/systemd/nspawn/locks", 0600);
+                mkdir_p("/run/systemd/nspawn/locks", 0700);
 
                 r = make_lock_file(p, operation, global);
                 if (r < 0) {
@@ -620,14 +755,20 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
 int image_set_limit(Image *i, uint64_t referenced_max) {
         assert(i);
 
-        if (path_equal(i->path, "/") ||
-            path_startswith(i->path, "/usr"))
+        if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i))
                 return -EROFS;
 
         if (i->type != IMAGE_SUBVOLUME)
                 return -EOPNOTSUPP;
 
-        return btrfs_quota_limit(i->path, referenced_max);
+        /* We set the quota both for the subvolume as well as for the
+         * subtree. The latter is mostly for historical reasons, since
+         * we didn't use to have a concept of subtree quota, and hence
+         * only modified the subvolume quota. */
+
+        (void) btrfs_qgroup_set_limit(i->path, 0, referenced_max);
+        (void) btrfs_subvol_auto_qgroup(i->path, 0, true);
+        return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max);
 }
 
 int image_name_lock(const char *name, int operation, LockFile *ret) {
@@ -644,7 +785,7 @@ int image_name_lock(const char *name, int operation, LockFile *ret) {
         if (streq(name, ".host"))
                 return -EBUSY;
 
-        mkdir_p("/run/systemd/nspawn/locks", 0600);
+        mkdir_p("/run/systemd/nspawn/locks", 0700);
         p = strjoina("/run/systemd/nspawn/locks/name-", name);
 
         return make_lock_file(p, operation, ret);