machined: add API for querying the OS release of a machine image
This adds a bus call GetImageOSRelease() to the Manager interface that retrieves the /etc/os-release file of a machine image. It matches the existing GetMachineOSRelease() call, however operates on a disk image rather than a running container. The backend for this call on .raw images is implemented via the generalized image dissector, which makes this scheme relatively easy to implement.
This commit is contained in:
parent
676bcb0fc0
commit
9153b02bb5
|
@ -17,14 +17,23 @@
|
|||
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "bus-label.h"
|
||||
#include "bus-util.h"
|
||||
#include "copy.h"
|
||||
#include "dissect-image.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "image-dbus.h"
|
||||
#include "io-util.h"
|
||||
#include "loop-util.h"
|
||||
#include "machine-image.h"
|
||||
#include "mount-util.h"
|
||||
#include "process-util.h"
|
||||
#include "raw-clone.h"
|
||||
#include "strv.h"
|
||||
#include "user-util.h"
|
||||
|
||||
|
@ -279,6 +288,161 @@ int bus_image_method_set_limit(
|
|||
return sd_bus_reply_method_return(message, NULL);
|
||||
}
|
||||
|
||||
#define EXIT_NOT_FOUND 2
|
||||
|
||||
static int directory_image_get_os_release(Image *image, char ***ret, sd_bus_error *error) {
|
||||
|
||||
_cleanup_free_ char *path = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
int r;
|
||||
|
||||
assert(image);
|
||||
assert(ret);
|
||||
|
||||
r = chase_symlinks("/etc/os-release", image->path, CHASE_PREFIX_ROOT, &path);
|
||||
if (r == -ENOENT)
|
||||
r = chase_symlinks("/usr/lib/os-release", image->path, CHASE_PREFIX_ROOT, &path);
|
||||
if (r == -ENOENT)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Image does not contain OS release information");
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to resolve %s: %m", image->path);
|
||||
|
||||
r = load_env_file_pairs(NULL, path, NULL, ret);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to open %s: %m", path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int raw_image_get_os_release(Image *image, char ***ret, sd_bus_error *error) {
|
||||
_cleanup_(rmdir_and_freep) char *t = NULL;
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
|
||||
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
|
||||
_cleanup_(sigkill_waitp) pid_t child = 0;
|
||||
_cleanup_close_pair_ int pair[2] = { -1, -1 };
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_strv_free_ char **v = NULL;
|
||||
siginfo_t si;
|
||||
int r;
|
||||
|
||||
assert(image);
|
||||
assert(ret);
|
||||
|
||||
r = mkdtemp_malloc("/tmp/machined-root-XXXXXX", &t);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to create temporary directory: %m");
|
||||
|
||||
r = loop_device_make_by_path(image->path, O_RDONLY, &d);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to set up loop block device for %s: %m", image->path);
|
||||
|
||||
r = dissect_image(d->fd, &m);
|
||||
if (r == -ENOPKG)
|
||||
return sd_bus_error_set_errnof(error, r, "Disk image %s not understood: %m", image->path);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to dissect image %s: %m", image->path);
|
||||
|
||||
if (pipe2(pair, O_CLOEXEC) < 0)
|
||||
return sd_bus_error_set_errnof(error, errno, "Failed to create communication pipe: %m");
|
||||
|
||||
child = raw_clone(SIGCHLD|CLONE_NEWNS);
|
||||
if (child < 0)
|
||||
return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
|
||||
|
||||
if (child == 0) {
|
||||
int fd;
|
||||
|
||||
pair[0] = safe_close(pair[0]);
|
||||
|
||||
/* Make sure we never propagate to the host */
|
||||
if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
r = dissected_image_mount(m, t, DISSECTED_IMAGE_READ_ONLY);
|
||||
if (r < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
r = mount_move_root(t);
|
||||
if (r < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
|
||||
if (fd < 0 && errno == ENOENT) {
|
||||
fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
|
||||
if (fd < 0 && errno == ENOENT)
|
||||
_exit(EXIT_NOT_FOUND);
|
||||
}
|
||||
if (fd < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
r = copy_bytes(fd, pair[1], (uint64_t) -1, false);
|
||||
if (r < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
pair[1] = safe_close(pair[1]);
|
||||
|
||||
f = fdopen(pair[0], "re");
|
||||
if (!f)
|
||||
return -errno;
|
||||
|
||||
pair[0] = -1;
|
||||
|
||||
r = load_env_file_pairs(f, "os-release", NULL, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = wait_for_terminate(child, &si);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
|
||||
child = 0;
|
||||
if (si.si_code == CLD_EXITED && si.si_status == EXIT_NOT_FOUND)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Image does not contain OS release information");
|
||||
if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
|
||||
|
||||
*ret = v;
|
||||
v = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bus_image_method_get_os_release(
|
||||
sd_bus_message *message,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT;
|
||||
_cleanup_strv_free_ char **v = NULL;
|
||||
Image *image = userdata;
|
||||
int r;
|
||||
|
||||
r = image_path_lock(image->path, LOCK_SH|LOCK_NB, &tree_global_lock, &tree_local_lock);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to lock image: %m");
|
||||
|
||||
switch (image->type) {
|
||||
|
||||
case IMAGE_DIRECTORY:
|
||||
case IMAGE_SUBVOLUME:
|
||||
r = directory_image_get_os_release(image, &v, error);
|
||||
break;
|
||||
|
||||
case IMAGE_RAW:
|
||||
r = raw_image_get_os_release(image, &v, error);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached("Unknown image type");
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return bus_reply_pair_array(message, v);
|
||||
}
|
||||
|
||||
const sd_bus_vtable image_vtable[] = {
|
||||
SD_BUS_VTABLE_START(0),
|
||||
SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0),
|
||||
|
@ -296,6 +460,7 @@ const sd_bus_vtable image_vtable[] = {
|
|||
SD_BUS_METHOD("Clone", "sb", NULL, bus_image_method_clone, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_image_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_VTABLE_END
|
||||
};
|
||||
|
||||
|
|
|
@ -33,3 +33,4 @@ int bus_image_method_rename(sd_bus_message *message, void *userdata, sd_bus_erro
|
|||
int bus_image_method_clone(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
int bus_image_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error);
|
||||
|
|
|
@ -356,11 +356,11 @@ int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd
|
|||
return sd_bus_send(NULL, reply, NULL);
|
||||
}
|
||||
|
||||
#define EXIT_NOT_FOUND 2
|
||||
|
||||
int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
Machine *m = userdata;
|
||||
char **k, **v;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
|
@ -394,7 +394,7 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s
|
|||
return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
|
||||
|
||||
if (child == 0) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
int fd = -1;
|
||||
|
||||
pair[0] = safe_close(pair[0]);
|
||||
|
||||
|
@ -402,12 +402,14 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s
|
|||
if (r < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
|
||||
if (fd < 0 && errno == ENOENT) {
|
||||
fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
|
||||
if (fd < 0 && errno == ENOENT)
|
||||
_exit(EXIT_NOT_FOUND);
|
||||
}
|
||||
if (fd < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
r = copy_bytes(fd, pair[1], (uint64_t) -1, false);
|
||||
if (r < 0)
|
||||
|
@ -431,6 +433,8 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s
|
|||
r = wait_for_terminate(child, &si);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
|
||||
if (si.si_code == CLD_EXITED && si.si_status == EXIT_NOT_FOUND)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Machine does not contain OS release information");
|
||||
if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
|
||||
|
||||
|
@ -441,25 +445,7 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s
|
|||
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting OS release data is only supported on container machines.");
|
||||
}
|
||||
|
||||
r = sd_bus_message_new_method_return(message, &reply);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_open_container(reply, 'a', "{ss}");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH_PAIR(k, v, l) {
|
||||
r = sd_bus_message_append(reply, "{ss}", *k, *v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = sd_bus_message_close_container(reply);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_bus_send(NULL, reply, NULL);
|
||||
return bus_reply_pair_array(message, l);
|
||||
}
|
||||
|
||||
int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
|
|
|
@ -42,3 +42,5 @@ int bus_machine_method_open_root_directory(sd_bus_message *message, void *userda
|
|||
|
||||
int machine_send_signal(Machine *m, bool new_machine);
|
||||
int machine_send_create_reply(Machine *m, sd_bus_error *error);
|
||||
|
||||
int bus_reply_pair_array(sd_bus_message *m, char **l);
|
||||
|
|
|
@ -138,7 +138,7 @@ static void clean_machine_info(MachineInfo *machines, size_t n_machines) {
|
|||
free(machines);
|
||||
}
|
||||
|
||||
static int get_os_release_property(sd_bus *bus, const char *name, const char *query, ...) {
|
||||
static int call_get_os_release(sd_bus *bus, const char *method, const char *name, const char *query, ...) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
const char *k, *v, *iter, **query_res = NULL;
|
||||
size_t count = 0, awaited_args = 0;
|
||||
|
@ -153,12 +153,13 @@ static int get_os_release_property(sd_bus *bus, const char *name, const char *qu
|
|||
awaited_args++;
|
||||
query_res = newa0(const char *, awaited_args);
|
||||
|
||||
r = sd_bus_call_method(bus,
|
||||
"org.freedesktop.machine1",
|
||||
"/org/freedesktop/machine1",
|
||||
"org.freedesktop.machine1.Manager",
|
||||
"GetMachineOSRelease",
|
||||
NULL, &reply, "s", name);
|
||||
r = sd_bus_call_method(
|
||||
bus,
|
||||
"org.freedesktop.machine1",
|
||||
"/org/freedesktop/machine1",
|
||||
"org.freedesktop.machine1.Manager",
|
||||
method,
|
||||
NULL, &reply, "s", name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -193,7 +194,7 @@ static int get_os_release_property(sd_bus *bus, const char *name, const char *qu
|
|||
val = strdup(query_res[count]);
|
||||
if (!val) {
|
||||
va_end(ap);
|
||||
return log_oom();
|
||||
return -ENOMEM;
|
||||
}
|
||||
*out = val;
|
||||
}
|
||||
|
@ -249,8 +250,12 @@ static int list_machines(int argc, char *argv[], void *userdata) {
|
|||
|
||||
machines[n_machines].os = NULL;
|
||||
machines[n_machines].version_id = NULL;
|
||||
r = get_os_release_property(bus, name,
|
||||
"ID\0" "VERSION_ID\0",
|
||||
r = call_get_os_release(
|
||||
bus,
|
||||
"GetMachineOSRelease",
|
||||
name,
|
||||
"ID\0"
|
||||
"VERSION_ID\0",
|
||||
&machines[n_machines].os,
|
||||
&machines[n_machines].version_id);
|
||||
if (r < 0)
|
||||
|
@ -610,7 +615,7 @@ static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *p
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int print_os_release(sd_bus *bus, const char *name, const char *prefix) {
|
||||
static int print_os_release(sd_bus *bus, const char *method, const char *name, const char *prefix) {
|
||||
_cleanup_free_ char *pretty = NULL;
|
||||
int r;
|
||||
|
||||
|
@ -618,7 +623,7 @@ static int print_os_release(sd_bus *bus, const char *name, const char *prefix) {
|
|||
assert(name);
|
||||
assert(prefix);
|
||||
|
||||
r = get_os_release_property(bus, name, "PRETTY_NAME\0", &pretty, NULL);
|
||||
r = call_get_os_release(bus, method, name, "PRETTY_NAME\0", &pretty, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -729,7 +734,7 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
|
|||
"\n\t ",
|
||||
ALL_IP_ADDRESSES);
|
||||
|
||||
print_os_release(bus, i->name, "\t OS: ");
|
||||
print_os_release(bus, "GetMachineOSRelease", i->name, "\t OS: ");
|
||||
|
||||
if (i->unit) {
|
||||
printf("\t Unit: %s\n", i->unit);
|
||||
|
@ -927,6 +932,8 @@ static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) {
|
|||
if (i->path)
|
||||
printf("\t Path: %s\n", i->path);
|
||||
|
||||
print_os_release(bus, "GetImageOSRelease", i->name, "\t OS: ");
|
||||
|
||||
printf("\t RO: %s%s%s\n",
|
||||
i->read_only ? ansi_highlight_red() : "",
|
||||
i->read_only ? "read-only" : "writable",
|
||||
|
|
|
@ -825,6 +825,30 @@ static int method_mark_image_read_only(sd_bus_message *message, void *userdata,
|
|||
return bus_image_method_mark_read_only(message, i, error);
|
||||
}
|
||||
|
||||
static int method_get_image_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
|
||||
_cleanup_(image_unrefp) Image *i = NULL;
|
||||
const char *name;
|
||||
int r;
|
||||
|
||||
assert(message);
|
||||
|
||||
r = sd_bus_message_read(message, "s", &name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!image_name_is_valid(name))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
|
||||
|
||||
r = image_find(name, &i);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
|
||||
|
||||
i->userdata = userdata;
|
||||
return bus_image_method_get_os_release(message, i, error);
|
||||
}
|
||||
|
||||
static int clean_pool_done(Operation *operation, int ret, sd_bus_error *error) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
|
@ -1396,6 +1420,7 @@ const sd_bus_vtable manager_vtable[] = {
|
|||
SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("GetImageOSRelease", "s", "a{ss}", method_get_image_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
SD_BUS_METHOD("CleanPool", "s", "a(st)", method_clean_pool, SD_BUS_VTABLE_UNPRIVILEGED),
|
||||
|
@ -1804,3 +1829,30 @@ int manager_add_machine(Manager *m, const char *name, Machine **_machine) {
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bus_reply_pair_array(sd_bus_message *m, char **l) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
char **k, **v;
|
||||
int r;
|
||||
|
||||
r = sd_bus_message_new_method_return(m, &reply);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_open_container(reply, 'a', "{ss}");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH_PAIR(k, v, l) {
|
||||
r = sd_bus_message_append(reply, "{ss}", *k, *v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = sd_bus_message_close_container(reply);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_bus_send(NULL, reply, NULL);
|
||||
|
||||
}
|
||||
|
|
|
@ -116,6 +116,10 @@
|
|||
send_interface="org.freedesktop.machine1.Manager"
|
||||
send_member="SetImageLimit"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.machine1"
|
||||
send_interface="org.freedesktop.machine1.Manager"
|
||||
send_member="GetImageOSRelease"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.machine1"
|
||||
send_interface="org.freedesktop.machine1.Manager"
|
||||
send_member="CleanPool"/>
|
||||
|
@ -192,6 +196,10 @@
|
|||
send_interface="org.freedesktop.machine1.Image"
|
||||
send_member="MarkReadOnly"/>
|
||||
|
||||
<allow send_destination="org.freedesktop.machine1"
|
||||
send_interface="org.freedesktop.machine1.Image"
|
||||
send_member="GetOSRelease"/>
|
||||
|
||||
<allow receive_sender="org.freedesktop.machine1"/>
|
||||
</policy>
|
||||
|
||||
|
|
Loading…
Reference in New Issue