chiark / gitweb /
machined/machinectl: add logic to show list of available images
authorLennart Poettering <lennart@poettering.net>
Fri, 19 Dec 2014 17:42:50 +0000 (18:42 +0100)
committerLennart Poettering <lennart@poettering.net>
Fri, 19 Dec 2014 18:19:29 +0000 (19:19 +0100)
This adds a new bus call to machined that enumerates /var/lib/container
and returns all trees stored in it, distuingishing three types:

        - GPT disk images, which are files suffixed with ".gpt"
        - directory trees
        - btrfs subvolumes

Makefile.am
src/machine/image.c [new file with mode: 0644]
src/machine/image.h [new file with mode: 0644]
src/machine/machine-dbus.c
src/machine/machinectl.c
src/machine/machined-dbus.c
src/machine/org.freedesktop.machine1.conf
src/shared/btrfs-util.c
src/shared/btrfs-util.h
src/shared/util.h
units/systemd-machined.service.in

index a7a2b6d..840b871 100644 (file)
@@ -5034,9 +5034,11 @@ rootlibexec_PROGRAMS += \
        systemd-machined
 
 libsystemd_machine_core_la_SOURCES = \
-       src/machine/machined-dbus.c \
        src/machine/machine.c \
        src/machine/machine.h \
+       src/machine/image.c \
+       src/machine/image.h \
+       src/machine/machined-dbus.c \
        src/machine/machine-dbus.c
 
 libsystemd_machine_core_la_LIBADD = \
diff --git a/src/machine/image.c b/src/machine/image.c
new file mode 100644 (file)
index 0000000..0ba9652
--- /dev/null
@@ -0,0 +1,239 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/statfs.h>
+
+#include "strv.h"
+#include "utf8.h"
+#include "btrfs-util.h"
+#include "image.h"
+#include "bus-label.h"
+
+Image *image_unref(Image *i) {
+        if (!i)
+                return NULL;
+
+        free(i->name);
+        free(i->path);
+        free(i);
+        return NULL;
+}
+
+static int add_image(
+                Hashmap *h,
+                ImageType t,
+                const char *name,
+                const char *path,
+                bool read_only,
+                usec_t mtime,
+                usec_t btime) {
+
+        _cleanup_(image_unrefp) Image *i = NULL;
+        int r;
+
+        assert(h);
+        assert(t >= 0);
+        assert(t < _IMAGE_TYPE_MAX);
+        assert(name);
+
+        i = new(Image, 1);
+        if (!i)
+                return -ENOMEM;
+
+        i->type = t;
+        i->read_only = read_only;
+        i->mtime = mtime;
+        i->btime = btime;
+
+        i->name = strdup(name);
+        if (!i->name)
+                return -ENOMEM;
+
+        if (path) {
+                i->path = strdup(path);
+                if (!i->path)
+                        return -ENOMEM;
+        }
+
+        r = hashmap_put(h, i->name, i);
+        if (r < 0)
+                return r;
+
+        i = NULL;
+        return 0;
+}
+
+int image_discover(Hashmap *h) {
+        const char *path;
+        int r;
+
+        assert(h);
+
+        FOREACH_STRING(path, "/var/lib/container", "/var/lib/machine") {
+                _cleanup_closedir_ DIR *d = NULL;
+                struct dirent *de;
+
+                d = opendir(path);
+                if (!d) {
+                        if (errno == ENOENT)
+                                return 0;
+
+                        return -errno;
+                }
+
+                FOREACH_DIRENT_ALL(de, d, return -errno) {
+                        struct stat st;
+
+                        if (STR_IN_SET(de->d_name, ".", ".."))
+                                continue;
+
+                        /* Temporary files for atomically creating new files */
+                        if (startswith(de->d_name, ".#"))
+                                continue;
+
+                        if (string_has_cc(de->d_name, NULL))
+                                continue;
+
+                        if (!utf8_is_valid(de->d_name))
+                                continue;
+
+                        if (hashmap_contains(h, de->d_name))
+                                continue;
+
+                        /* We explicitly *do* follow symlinks here,
+                         * since we want to allow symlinking trees
+                         * into /var/lib/container/, and treat them
+                         * normally. */
+                        if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
+                                if (errno == ENOENT)
+                                        continue;
+
+                                return -errno;
+                        }
+
+                        if (S_ISDIR(st.st_mode)) {
+
+                                /* btrfs subvolumes have inode 256 */
+                                if (st.st_ino == 256) {
+                                        _cleanup_close_ int fd = -1;
+                                        struct statfs sfs;
+
+                                        fd = openat(dirfd(d), de->d_name, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
+                                        if (fd < 0) {
+                                                if (errno == ENOENT)
+                                                        continue;
+
+                                                return -errno;
+                                        }
+
+                                        if (fstatfs(fd, &sfs) < 0)
+                                                return -errno;
+
+                                        if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC)) {
+                                                usec_t btime = 0;
+                                                int ro;
+
+                                                /* It's a btrfs subvolume */
+
+                                                ro = btrfs_subvol_is_read_only_fd(fd);
+                                                if (ro < 0)
+                                                        return ro;
+
+                                                /* r = btrfs_subvol_get_btime(fd, &btime); */
+                                                /* if (r < 0) */
+                                                /*         return r; */
+
+                                                r = add_image(h,
+                                                              IMAGE_SUBVOLUME,
+                                                              de->d_name,
+                                                              path,
+                                                              ro,
+                                                              0,
+                                                              btime);
+
+                                                if (r < 0)
+                                                        return r;
+
+                                                continue;
+                                        }
+                                }
+
+                                /* It's just a normal directory. */
+
+                                r = add_image(h,
+                                              IMAGE_DIRECTORY,
+                                              de->d_name,
+                                              path,
+                                              false,
+                                              0,
+                                              0);
+                                if (r < 0)
+                                        return r;
+
+                        } else if (S_ISREG(st.st_mode) &&
+                                   endswith(de->d_name, ".gpt")) {
+
+                                /* It's a GPT block device */
+
+                                r = add_image(h,
+                                              IMAGE_GPT,
+                                              de->d_name,
+                                              path,
+                                              !!(st.st_mode & 0111),
+                                              timespec_load(&st.st_mtim),
+                                              0);
+                                if (r < 0)
+                                        return r;
+                        }
+                }
+        }
+
+        return 0;
+}
+
+void image_hashmap_free(Hashmap *map) {
+        Image *i;
+
+        while ((i = hashmap_steal_first(map)))
+                image_unref(i);
+
+        hashmap_free(map);
+}
+
+char *image_bus_path(const char *name) {
+        _cleanup_free_ char *e = NULL;
+
+        assert(name);
+
+        e = bus_label_escape(name);
+        if (!e)
+                return NULL;
+
+        return strappend("/org/freedesktop/machine1/image/", e);
+}
+
+static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
+        [IMAGE_DIRECTORY] = "directory",
+        [IMAGE_SUBVOLUME] = "subvolume",
+        [IMAGE_GPT] = "gpt",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);
diff --git a/src/machine/image.h b/src/machine/image.h
new file mode 100644 (file)
index 0000000..c77fd19
--- /dev/null
@@ -0,0 +1,57 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "time-util.h"
+#include "hashmap.h"
+
+typedef enum ImageType {
+        IMAGE_DIRECTORY,
+        IMAGE_SUBVOLUME,
+        IMAGE_GPT,
+        _IMAGE_TYPE_MAX,
+        _IMAGE_TYPE_INVALID = -1
+} ImageType;
+
+typedef struct Image {
+        ImageType type;
+        char *name;
+        char *path;
+        bool read_only;
+
+        usec_t mtime;
+        usec_t btime;
+} Image;
+
+Image *image_unref(Image *i);
+
+void image_hashmap_free(Hashmap *map);
+
+int image_discover(Hashmap *map);
+
+char *image_bus_path(const char *name);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Image*, image_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, image_hashmap_free);
+
+const char* image_type_to_string(ImageType t) _const_;
+ImageType image_type_from_string(const char *s) _pure_;
index f6fd9cf..163c73d 100644 (file)
@@ -32,6 +32,7 @@
 #include "fileio.h"
 #include "in-addr-util.h"
 #include "local-addresses.h"
+#include "image.h"
 #include "machine.h"
 
 static int property_get_id(
@@ -475,9 +476,11 @@ char *machine_bus_path(Machine *m) {
 }
 
 int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
+        _cleanup_(image_hashmap_freep) Hashmap *images = NULL;
         _cleanup_strv_free_ char **l = NULL;
         Machine *machine = NULL;
         Manager *m = userdata;
+        Image *image;
         Iterator i;
         int r;
 
@@ -497,6 +500,26 @@ int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char
                         return r;
         }
 
+        images = hashmap_new(&string_hash_ops);
+        if (!images)
+                return -ENOMEM;
+
+        r = image_discover(images);
+        if (r < 0)
+                return r;
+
+        HASHMAP_FOREACH(image, images, i) {
+                char *p;
+
+                p = image_bus_path(image->name);
+                if (!p)
+                        return -ENOMEM;
+
+                r = strv_consume(&l, p);
+                if (r < 0)
+                        return r;
+        }
+
         *nodes = l;
         l = NULL;
 
index a62ffe3..d2d3d16 100644 (file)
@@ -120,6 +120,98 @@ static int list_machines(sd_bus *bus, char **args, unsigned n) {
         return 0;
 }
 
+typedef struct ImageInfo {
+        const char *name;
+        const char *type;
+        bool read_only;
+} ImageInfo;
+
+static int compare_image_info(const void *a, const void *b) {
+        const ImageInfo *x = a, *y = b;
+
+        return strcmp(x->name, y->name);
+}
+
+static int list_images(sd_bus *bus, char **args, unsigned n) {
+
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+        size_t max_name = strlen("NAME"), max_type = strlen("TYPE");
+        _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+        _cleanup_free_ ImageInfo *images = NULL;
+        size_t n_images = 0, n_allocated = 0, j;
+        const char *name, *type, *object;
+        int read_only;
+        int r;
+
+        pager_open_if_enabled();
+
+        r = sd_bus_call_method(
+                                bus,
+                                "org.freedesktop.machine1",
+                                "/org/freedesktop/machine1",
+                                "org.freedesktop.machine1.Manager",
+                                "ListImages",
+                                &error,
+                                &reply,
+                                "");
+        if (r < 0) {
+                log_error("Could not get images: %s", bus_error_message(&error, -r));
+                return r;
+        }
+
+        r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbo)");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        while ((r = sd_bus_message_read(reply, "(ssbo)", &name, &type, &read_only, &object)) > 0) {
+
+                if (name[0] == '.' && !arg_all)
+                        continue;
+
+                if (!GREEDY_REALLOC(images, n_allocated, n_images + 1))
+                        return log_oom();
+
+                images[n_images].name = name;
+                images[n_images].type = type;
+                images[n_images].read_only = read_only;
+
+                if (strlen(name) > max_name)
+                        max_name = strlen(name);
+
+                if (strlen(type) > max_type)
+                        max_type = strlen(type);
+
+                n_images++;
+        }
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info);
+
+        if (arg_legend)
+                printf("%-*s %-*s %-3s\n", (int) max_name, "NAME", (int) max_type, "TYPE", "RO");
+
+        for (j = 0; j < n_images; j++) {
+                printf("%-*s %-*s %-3s\n",
+                       (int) max_name, images[j].name,
+                       (int) max_type, images[j].type,
+                       yes_no(images[j].read_only));
+        }
+
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+
+        if (arg_legend)
+                printf("\n%zu images listed.\n", n_images);
+
+        return 0;
+}
+
 static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) {
         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -1106,7 +1198,7 @@ static void help(void) {
                "  -s --signal=SIGNAL          Which signal to send\n"
                "     --read-only              Create read-only bind mount\n"
                "     --mkdir                  Create directory before bind mounting, if missing\n\n"
-               "Commands:\n"
+               "Machine Commands:\n"
                "  list                        List running VMs and containers\n"
                "  status NAME...              Show VM/container status\n"
                "  show NAME...                Show properties of one or more VMs/containers\n"
@@ -1117,7 +1209,9 @@ static void help(void) {
                "  terminate NAME...           Terminate one or more VMs/containers\n"
                "  bind NAME PATH [PATH]       Bind mount a path from the host into a container\n"
                "  copy-to NAME PATH [PATH]    Copy files from the host to a container\n"
-               "  copy-from NAME PATH [PATH]  Copy files from a container to the host\n",
+               "  copy-from NAME PATH [PATH]  Copy files from a container to the host\n\n"
+               "Image commands:\n"
+               "  list-images                 Show available images\n",
                program_invocation_short_name);
 }
 
@@ -1247,6 +1341,7 @@ static int machinectl_main(sd_bus *bus, int argc, char *argv[]) {
                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
         } verbs[] = {
                 { "list",                  LESS,   1, list_machines     },
+                { "list-images",           LESS,   1, list_images       },
                 { "status",                MORE,   2, show              },
                 { "show",                  MORE,   1, show              },
                 { "terminate",             MORE,   2, terminate_machine },
index 0b57b36..949c7d6 100644 (file)
@@ -39,6 +39,7 @@
 #include "bus-common-errors.h"
 #include "time-util.h"
 #include "cgroup-util.h"
+#include "image.h"
 #include "machined.h"
 
 static int method_get_machine(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) {
@@ -436,11 +437,63 @@ static int method_get_machine_os_release(sd_bus *bus, sd_bus_message *message, v
         return bus_machine_method_get_os_release(bus, message, machine, error);
 }
 
+static int method_list_images(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+        _cleanup_(image_hashmap_freep) Hashmap *images = NULL;
+        Manager *m = userdata;
+        Image *image;
+        Iterator i;
+        int r;
+
+        assert(bus);
+        assert(message);
+        assert(m);
+
+        images = hashmap_new(&string_hash_ops);
+        if (!images)
+                return -ENOMEM;
+
+        r = image_discover(images);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_new_method_return(message, &reply);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_open_container(reply, 'a', "(ssbo)");
+        if (r < 0)
+                return r;
+
+        HASHMAP_FOREACH(image, images, i) {
+                _cleanup_free_ char *p = NULL;
+
+                p = image_bus_path(image->name);
+                if (!p)
+                        return -ENOMEM;
+
+                r = sd_bus_message_append(reply, "(ssbo)",
+                                          image->name,
+                                          image_type_to_string(image->type),
+                                          image->read_only,
+                                          p);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_close_container(reply);
+        if (r < 0)
+                return r;
+
+        return sd_bus_send(bus, reply, NULL);
+}
+
 const sd_bus_vtable manager_vtable[] = {
         SD_BUS_VTABLE_START(0),
         SD_BUS_METHOD("GetMachine", "s", "o", method_get_machine, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("GetMachineByPID", "u", "o", method_get_machine_by_pid, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("ListMachines", NULL, "a(ssso)", method_list_machines, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("ListImages", NULL, "a(ssbo)", method_list_images, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("CreateMachine", "sayssusa(sv)", "o", method_create_machine, 0),
         SD_BUS_METHOD("CreateMachineWithNetwork", "sayssusaia(sv)", "o", method_create_machine_with_network, 0),
         SD_BUS_METHOD("RegisterMachine", "sayssus", "o", method_register_machine, 0),
index ac1aee8..3745c52 100644 (file)
 
                 <allow send_destination="org.freedesktop.machine1"
                        send_interface="org.freedesktop.machine1.Manager"
+                       send_member="ListImages"/>
+
+                <allow send_destination="org.freedesktop.machine1"
+                       send_interface="org.freedesktop.machine1.Manager"
                        send_member="GetMachine"/>
 
                 <allow send_destination="org.freedesktop.machine1"
index 492d7fc..d685b3e 100644 (file)
@@ -84,18 +84,18 @@ int btrfs_is_snapshot(int fd) {
         struct stat st;
         struct statfs sfs;
 
-        if (fstatfs(fd, &sfs) < 0)
+        /* On btrfs subvolumes always have the inode 256 */
+
+        if (fstat(fd, &st) < 0)
                 return -errno;
 
-        if (!F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC))
+        if (!S_ISDIR(st.st_mode) || st.st_ino != 256)
                 return 0;
 
-        if (fstat(fd, &st) < 0)
+        if (fstatfs(fd, &sfs) < 0)
                 return -errno;
 
-        /* On btrfs subvolumes always have the inode 256 */
-
-        return S_ISDIR(st.st_mode) && st.st_ino == 256;
+        return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC);
 }
 
 int btrfs_subvol_snapshot(const char *old_path, const char *new_path, bool read_only, bool fallback_copy) {
@@ -232,6 +232,15 @@ int btrfs_subvol_read_only(const char *path, bool b) {
         return 0;
 }
 
+int btrfs_subvol_is_read_only_fd(int fd) {
+        uint64_t flags;
+
+        if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0)
+                return -errno;
+
+        return !!(flags & BTRFS_SUBVOL_RDONLY);
+}
+
 int btrfs_reflink(int infd, int outfd) {
         int r;
 
index 28dbeb4..c8f798b 100644 (file)
@@ -28,6 +28,7 @@ int btrfs_subvol_make(const char *path);
 int btrfs_subvol_remove(const char *path);
 int btrfs_subvol_snapshot(const char *old_path, const char *new_path, bool read_only, bool fallback_copy);
 int btrfs_subvol_read_only(const char *path, bool b);
+int btrfs_subvol_is_read_only_fd(int fd);
 
 int btrfs_reflink(int infd, int outfd);
 
index 96b8c1b..e783ec6 100644 (file)
@@ -775,6 +775,15 @@ int search_and_fopen_nulstr(const char *path, const char *mode, const char *root
                         continue;                                       \
                 else
 
+#define FOREACH_DIRENT_ALL(de, d, on_error)                             \
+        for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d))   \
+                if (!de) {                                              \
+                        if (errno > 0) {                                \
+                                on_error;                               \
+                        }                                               \
+                        break;                                          \
+                } else
+
 static inline void *mempset(void *s, int c, size_t n) {
         memset(s, c, n);
         return (uint8_t*)s + n;
index bbb13de..15f34d9 100644 (file)
@@ -15,7 +15,7 @@ After=machine.slice
 [Service]
 ExecStart=@rootlibexecdir@/systemd-machined
 BusName=org.freedesktop.machine1
-CapabilityBoundingSet=CAP_KILL CAP_SYS_PTRACE CAP_SYS_ADMIN CAP_SETGID CAP_SYS_CHROOT
+CapabilityBoundingSet=CAP_KILL CAP_SYS_PTRACE CAP_SYS_ADMIN CAP_SETGID CAP_SYS_CHROOT CAP_DAC_READ_SEARCH
 WatchdogSec=1min
 PrivateTmp=yes
 PrivateDevices=yes