machined/machinectl: add logic to show list of available images

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
This commit is contained in:
Lennart Poettering 2014-12-19 18:42:50 +01:00
parent 8eebf6ad55
commit cd61c3bfd7
11 changed files with 503 additions and 11 deletions

View 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 = \

239
src/machine/image.c Normal file
View file

@ -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);

57
src/machine/image.h Normal file
View file

@ -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_;

View 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;

View 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 },

View 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),

View file

@ -40,6 +40,10 @@
send_interface="org.freedesktop.machine1.Manager"
send_member="ListMachines"/>
<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"/>

View file

@ -84,18 +84,18 @@ int btrfs_is_snapshot(int fd) {
struct stat st;
struct statfs sfs;
if (fstatfs(fd, &sfs) < 0)
return -errno;
if (!F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC))
return 0;
/* On btrfs subvolumes always have the inode 256 */
if (fstat(fd, &st) < 0)
return -errno;
/* On btrfs subvolumes always have the inode 256 */
if (!S_ISDIR(st.st_mode) || st.st_ino != 256)
return 0;
return S_ISDIR(st.st_mode) && st.st_ino == 256;
if (fstatfs(fd, &sfs) < 0)
return -errno;
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;

View 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);

View 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;

View 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