diff --git a/src/basic/hostname-util.c b/src/basic/hostname-util.c index 78f18e26bf..12a579b38a 100644 --- a/src/basic/hostname-util.c +++ b/src/basic/hostname-util.c @@ -25,6 +25,8 @@ #include #include +#include "alloc-util.h" +#include "def.h" #include "fd-util.h" #include "fileio.h" #include "hostname-util.h" @@ -219,35 +221,55 @@ int sethostname_idempotent(const char *s) { return 1; } -int read_hostname_config(const char *path, char **hostname) { - _cleanup_fclose_ FILE *f = NULL; - char l[LINE_MAX]; - char *name = NULL; +int read_etc_hostname_stream(FILE *f, char **ret) { + int r; - assert(path); - assert(hostname); + assert(f); + assert(ret); + + for (;;) { + _cleanup_free_ char *line = NULL; + char *p; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */ + return -ENOENT; + + p = strstrip(line); + + /* File may have empty lines or comments, ignore them */ + if (!IN_SET(*p, '\0', '#')) { + char *copy; + + hostname_cleanup(p); /* normalize the hostname */ + + if (!hostname_is_valid(p, true)) /* check that the hostname we return is valid */ + return -EBADMSG; + + copy = strdup(p); + if (!copy) + return -ENOMEM; + + *ret = copy; + return 0; + } + } +} + +int read_etc_hostname(const char *path, char **ret) { + _cleanup_fclose_ FILE *f = NULL; + + assert(ret); + + if (!path) + path = "/etc/hostname"; f = fopen(path, "re"); if (!f) return -errno; - /* may have comments, ignore them */ - FOREACH_LINE(l, f, return -errno) { - truncate_nl(l); - if (!IN_SET(l[0], '\0', '#')) { - /* found line with value */ - name = hostname_cleanup(l); - name = strdup(name); - if (!name) - return -ENOMEM; - break; - } - } + return read_etc_hostname_stream(f, ret); - if (!name) - /* no non-empty line found */ - return -ENOENT; - - *hostname = name; - return 0; } diff --git a/src/basic/hostname-util.h b/src/basic/hostname-util.h index a7d09c910b..52fd6b0899 100644 --- a/src/basic/hostname-util.h +++ b/src/basic/hostname-util.h @@ -39,4 +39,5 @@ bool is_gateway_hostname(const char *hostname); int sethostname_idempotent(const char *s); -int read_hostname_config(const char *path, char **hostname); +int read_etc_hostname_stream(FILE *f, char **ret); +int read_etc_hostname(const char *path, char **ret); diff --git a/src/basic/signal-util.h b/src/basic/signal-util.h index 294f226ed0..76b239b1fc 100644 --- a/src/basic/signal-util.h +++ b/src/basic/signal-util.h @@ -45,11 +45,11 @@ static inline void block_signals_reset(sigset_t *ss) { assert_se(sigprocmask(SIG_SETMASK, ss, NULL) >= 0); } -#define BLOCK_SIGNALS(...) \ - _cleanup_(block_signals_reset) _unused_ sigset_t _saved_sigset = ({ \ - sigset_t t; \ - assert_se(sigprocmask_many(SIG_BLOCK, &t, __VA_ARGS__, -1) >= 0); \ - t; \ +#define BLOCK_SIGNALS(...) \ + _cleanup_(block_signals_reset) _unused_ sigset_t _saved_sigset = ({ \ + sigset_t _t; \ + assert_se(sigprocmask_many(SIG_BLOCK, &_t, __VA_ARGS__, -1) >= 0); \ + _t; \ }) static inline bool SIGNAL_VALID(int signo) { diff --git a/src/basic/strv.h b/src/basic/strv.h index c793b2cf51..0ed6b74330 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -181,3 +181,11 @@ char **strv_skip(char **l, size_t n); int strv_extend_n(char ***l, const char *value, size_t n); int fputstrv(FILE *f, char **l, const char *separator, bool *space); + +#define strv_free_and_replace(a, b) \ + ({ \ + strv_free(a); \ + (a) = (b); \ + (b) = NULL; \ + 0; \ + }) diff --git a/src/core/hostname-setup.c b/src/core/hostname-setup.c index f905f52991..19299918cc 100644 --- a/src/core/hostname-setup.c +++ b/src/core/hostname-setup.c @@ -37,7 +37,7 @@ int hostname_setup(void) { const char *hn; int r; - r = read_hostname_config("/etc/hostname", &b); + r = read_etc_hostname(NULL, &b); if (r < 0) { if (r == -ENOENT) enoent = true; diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 42957766ea..0210e46372 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -28,6 +28,7 @@ #include "log.h" #include "loop-util.h" #include "string-util.h" +#include "strv.h" #include "util.h" static enum { @@ -264,6 +265,36 @@ int main(int argc, char *argv[]) { putchar('\n'); } + r = dissected_image_acquire_metadata(m); + if (r < 0) { + log_error_errno(r, "Failed to acquire image metadata: %m"); + goto finish; + } + + if (m->hostname) + printf(" Hostname: %s\n", m->hostname); + + if (!sd_id128_is_null(m->machine_id)) + printf("Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->machine_id)); + + if (!strv_isempty(m->machine_info)) { + char **p, **q; + + STRV_FOREACH_PAIR(p, q, m->machine_info) + printf("%s %s=%s\n", + p == m->machine_info ? "Mach. Info:" : " ", + *p, *q); + } + + if (!strv_isempty(m->os_release)) { + char **p, **q; + + STRV_FOREACH_PAIR(p, q, m->os_release) + printf("%s %s=%s\n", + p == m->os_release ? "OS Release:" : " ", + *p, *q); + } + break; } diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index a9d5d49bdd..5feaa60c99 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -96,7 +96,7 @@ static int context_read_data(Context *c) { if (!c->data[PROP_HOSTNAME]) return -ENOMEM; - r = read_hostname_config("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]); + r = read_etc_hostname(NULL, &c->data[PROP_STATIC_HOSTNAME]); if (r < 0 && r != -ENOENT) return r; diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index c868d623ba..3d2fdd496c 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -290,124 +290,68 @@ int bus_image_method_set_limit( return sd_bus_reply_method_return(message, NULL); } -#define EXIT_NOT_FOUND 2 +int bus_image_method_get_hostname( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { -static int directory_image_get_os_release(Image *image, char ***ret, sd_bus_error *error) { - - _cleanup_free_ char *path = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Image *image = userdata; 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, NULL, 0, DISSECT_IMAGE_REQUIRE_ROOT, &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, DISSECT_IMAGE_READ_ONLY); + if (!image->metadata_valid) { + r = image_read_metadata(image); 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, 0); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); + return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); } - pair[1] = safe_close(pair[1]); + return sd_bus_reply_method_return(message, "s", image->hostname); +} - f = fdopen(pair[0], "re"); - if (!f) - return -errno; +int bus_image_method_get_machine_id( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { - pair[0] = -1; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Image *image = userdata; + int r; - r = load_env_file_pairs(f, "os-release", NULL, &v); + if (!image->metadata_valid) { + r = image_read_metadata(image); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); + } + + r = sd_bus_message_new_method_return(message, &reply); if (r < 0) return r; - r = wait_for_terminate(child, &si); + if (sd_id128_is_null(image->machine_id)) /* Add an empty array if the ID is zero */ + r = sd_bus_message_append(reply, "ay", 0); + else + r = sd_bus_message_append_array(reply, 'y', image->machine_id.bytes, 16); 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."); + return r; - *ret = v; - v = NULL; + return sd_bus_send(NULL, reply, NULL); +} - return 0; +int bus_image_method_get_machine_info( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + int r; + + if (!image->metadata_valid) { + r = image_read_metadata(image); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); + } + + return bus_reply_pair_array(message, image->machine_info); } int bus_image_method_get_os_release( @@ -415,34 +359,16 @@ int bus_image_method_get_os_release( 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: - case IMAGE_BLOCK: - r = raw_image_get_os_release(image, &v, error); - break; - - default: - assert_not_reached("Unknown image type"); + if (!image->metadata_valid) { + r = image_read_metadata(image); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); } - if (r < 0) - return r; - return bus_reply_pair_array(message, v); + return bus_reply_pair_array(message, image->os_release); } const sd_bus_vtable image_vtable[] = { @@ -462,6 +388,9 @@ 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("GetHostname", NULL, "s", bus_image_method_get_hostname, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineID", NULL, "ay", bus_image_method_get_machine_id, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineInfo", NULL, "a{ss}", bus_image_method_get_machine_info, 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 }; diff --git a/src/machine/image-dbus.h b/src/machine/image-dbus.h index f270e57a9a..119c69c25a 100644 --- a/src/machine/image-dbus.h +++ b/src/machine/image-dbus.h @@ -34,4 +34,7 @@ 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_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_get_machine_id(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_get_machine_info(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); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 0c7d423ddb..a8eab2825c 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -900,6 +900,100 @@ static int show_machine(int argc, char *argv[], void *userdata) { return r; } +static int print_image_hostname(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *hn; + int r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetImageHostname", + NULL, &reply, "s", name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "s", &hn); + if (r < 0) + return r; + + if (!isempty(hn)) + printf("\tHostname: %s\n", hn); + + return 0; +} + +static int print_image_machine_id(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + sd_id128_t id = SD_ID128_NULL; + const void *p; + size_t size; + int r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetImageMachineID", + NULL, &reply, "s", name); + if (r < 0) + return r; + + r = sd_bus_message_read_array(reply, 'y', &p, &size); + if (r < 0) + return r; + + if (size == sizeof(sd_id128_t)) + memcpy(&id, p, size); + + if (!sd_id128_is_null(id)) + printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id)); + + return 0; +} + +static int print_image_machine_info(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_strv_free_ char **l = NULL; + int r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetImageMachineInfo", + NULL, &reply, "s", name); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + if (r < 0) + return r; + + for (;;) { + const char *p, *q; + + r = sd_bus_message_read(reply, "{ss}", &p, &q); + if (r < 0) + return r; + if (r == 0) + break; + + if (streq(p, "DEPLOYMENT")) + printf(" Deployment: %s\n", q); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + return 0; +} + typedef struct ImageStatusInfo { char *name; char *path; @@ -914,12 +1008,13 @@ typedef struct ImageStatusInfo { } ImageStatusInfo; static void image_status_info_clear(ImageStatusInfo *info) { - if (info) { - free(info->name); - free(info->path); - free(info->type); - zero(*info); - } + if (!info) + return; + + free(info->name); + free(info->path); + free(info->type); + zero(*info); } static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { @@ -942,6 +1037,10 @@ static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { if (i->path) printf("\t Path: %s\n", i->path); + (void) print_image_hostname(bus, i->name); + (void) print_image_machine_id(bus, i->name); + (void) print_image_machine_info(bus, i->name); + print_os_release(bus, "GetImageOSRelease", i->name, "\t OS: "); printf("\t RO: %s%s%s\n", diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index f537f3a465..8f460d0617 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -846,6 +846,78 @@ 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_hostname(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_hostname(message, i, error); +} + +static int method_get_image_machine_id(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_machine_id(message, i, error); +} + +static int method_get_image_machine_info(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_machine_info(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; @@ -1442,6 +1514,9 @@ 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("GetImageHostname", "s", "s", method_get_image_hostname, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetImageMachineID", "s", "ay", method_get_image_machine_id, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetImageMachineInfo", "s", "a{ss}", method_get_image_machine_info, 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), diff --git a/src/machine/org.freedesktop.machine1.conf b/src/machine/org.freedesktop.machine1.conf index daa365a9dd..73b7e07103 100644 --- a/src/machine/org.freedesktop.machine1.conf +++ b/src/machine/org.freedesktop.machine1.conf @@ -120,6 +120,18 @@ send_interface="org.freedesktop.machine1.Manager" send_member="SetImageLimit"/> + + + + + + @@ -204,6 +216,18 @@ send_interface="org.freedesktop.machine1.Image" send_member="MarkReadOnly"/> + + + + + + diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 5bb0d3a478..08f1e98640 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -25,19 +25,28 @@ #endif #endif #include +#include +#include #include "architecture.h" #include "ask-password-api.h" #include "blkid-util.h" +#include "copy.h" +#include "def.h" #include "dissect-image.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "gpt.h" #include "hexdecoct.h" +#include "hostname-util.h" +#include "id128-util.h" #include "linux-3.13/dm-ioctl.h" #include "mount-util.h" #include "path-util.h" +#include "process-util.h" +#include "raw-clone.h" +#include "signal-util.h" #include "stat-util.h" #include "stdio-util.h" #include "string-table.h" @@ -635,6 +644,10 @@ DissectedImage* dissected_image_unref(DissectedImage *m) { free(m->partitions[i].decrypted_node); } + free(m->hostname); + strv_free(m->machine_info); + strv_free(m->os_release); + free(m); return NULL; } @@ -1193,6 +1206,174 @@ int root_hash_load(const char *image, void **ret, size_t *ret_size) { return 1; } +int dissected_image_acquire_metadata(DissectedImage *m) { + + enum { + META_HOSTNAME, + META_MACHINE_ID, + META_MACHINE_INFO, + META_OS_RELEASE, + _META_MAX, + }; + + static const char *const paths[_META_MAX] = { + [META_HOSTNAME] = "/etc/hostname\0", + [META_MACHINE_ID] = "/etc/machine-id\0", + [META_MACHINE_INFO] = "/etc/machine-info\0", + [META_OS_RELEASE] = "/etc/os-release\0/usr/lib/os-release\0", + }; + + _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL; + _cleanup_(rmdir_and_freep) char *t = NULL; + _cleanup_(sigkill_waitp) pid_t child = 0; + sd_id128_t machine_id = SD_ID128_NULL; + _cleanup_free_ char *hostname = NULL; + unsigned n_meta_initialized = 0, k; + int fds[2 * _META_MAX], r; + siginfo_t si; + + BLOCK_SIGNALS(SIGCHLD); + + assert(m); + + for (; n_meta_initialized < _META_MAX; n_meta_initialized ++) + if (pipe2(fds + 2*n_meta_initialized, O_CLOEXEC) < 0) { + r = -errno; + goto finish; + } + + r = mkdtemp_malloc("/tmp/dissect-XXXXXX", &t); + if (r < 0) + goto finish; + + child = raw_clone(SIGCHLD|CLONE_NEWNS); + if (child < 0) { + r = -errno; + goto finish; + } + + if (child == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 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, DISSECT_IMAGE_READ_ONLY); + if (r < 0) + _exit(EXIT_FAILURE); + + for (k = 0; k < _META_MAX; k++) { + _cleanup_close_ int fd = -1; + const char *p; + + fds[2*k] = safe_close(fds[2*k]); + + NULSTR_FOREACH(p, paths[k]) { + _cleanup_free_ char *q = NULL; + + r = chase_symlinks(p, t, CHASE_PREFIX_ROOT, &q); + if (r < 0) + continue; + + fd = open(q, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd >= 0) + break; + } + if (fd < 0) + continue; + + r = copy_bytes(fd, fds[2*k+1], (uint64_t) -1, 0); + if (r < 0) + _exit(EXIT_FAILURE); + + fds[2*k+1] = safe_close(fds[2*k+1]); + } + + _exit(EXIT_SUCCESS); + } + + for (k = 0; k < _META_MAX; k++) { + _cleanup_fclose_ FILE *f = NULL; + + fds[2*k+1] = safe_close(fds[2*k+1]); + + f = fdopen(fds[2*k], "re"); + if (!f) { + r = -errno; + goto finish; + } + + fds[2*k] = -1; + + switch (k) { + + case META_HOSTNAME: + r = read_etc_hostname_stream(f, &hostname); + if (r < 0) + log_debug_errno(r, "Failed to read /etc/hostname: %m"); + + break; + + case META_MACHINE_ID: { + _cleanup_free_ char *line = NULL; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + log_debug_errno(r, "Failed to read /etc/machine-id: %m"); + else if (r == 33) { + r = sd_id128_from_string(line, &machine_id); + if (r < 0) + log_debug_errno(r, "Image contains invalid /etc/machine-id: %s", line); + } else if (r == 0) + log_debug("/etc/machine-id file is empty."); + else + log_debug("/etc/machine-id has unexpected length %i.", r); + + break; + } + + case META_MACHINE_INFO: + r = load_env_file_pairs(f, "machine-info", NULL, &machine_info); + if (r < 0) + log_debug_errno(r, "Failed to read /etc/machine-info: %m"); + + break; + + case META_OS_RELEASE: + r = load_env_file_pairs(f, "os-release", NULL, &os_release); + if (r < 0) + log_debug_errno(r, "Failed to read OS release file: %m"); + + break; + } + } + + r = wait_for_terminate(child, &si); + if (r < 0) + goto finish; + child = 0; + + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) { + r = -EPROTO; + goto finish; + } + + free_and_replace(m->hostname, hostname); + m->machine_id = machine_id; + strv_free_and_replace(m->machine_info, machine_info); + strv_free_and_replace(m->os_release, os_release); + +finish: + for (k = 0; k < n_meta_initialized; k++) + safe_close_pair(fds + 2*k); + + return r; +} + static const char *const partition_designator_table[] = { [PARTITION_ROOT] = "root", [PARTITION_ROOT_SECONDARY] = "root-secondary", diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 20afbb5bf6..30a12cb540 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -77,7 +77,13 @@ struct DissectedImage { bool encrypted:1; bool verity:1; /* verity available and usable */ bool can_verity:1; /* verity available, but not necessarily used */ + DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX]; + + char *hostname; + sd_id128_t machine_id; + char **machine_info; + char **os_release; }; int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DissectedImage **ret); @@ -89,6 +95,8 @@ int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const voi int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DecryptedImage **ret); int dissected_image_mount(DissectedImage *m, const char *dest, DissectImageFlags flags); +int dissected_image_acquire_metadata(DissectedImage *m); + DecryptedImage* decrypted_image_unref(DecryptedImage *p); DEFINE_TRIVIAL_CLEANUP_FUNC(DecryptedImage*, decrypted_image_unref); int decrypted_image_relinquish(DecryptedImage *d); diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c index a9e5d608a5..2dff66d00a 100644 --- a/src/shared/machine-image.c +++ b/src/shared/machine-image.c @@ -34,12 +34,17 @@ #include "chattr-util.h" #include "copy.h" #include "dirent-util.h" +#include "dissect-image.h" #include "env-util.h" #include "fd-util.h" +#include "fileio.h" #include "fs-util.h" #include "hashmap.h" +#include "hostname-util.h" +#include "id128-util.h" #include "lockfile-util.h" #include "log.h" +#include "loop-util.h" #include "machine-image.h" #include "macro.h" #include "mkdir.h" @@ -65,6 +70,11 @@ Image *image_unref(Image *i) { free(i->name); free(i->path); + + free(i->hostname); + strv_free(i->machine_info); + strv_free(i->os_release); + return mfree(i); } @@ -761,6 +771,7 @@ int image_clone(Image *i, const char *new_name, bool read_only) { int image_read_only(Image *i, bool b) { _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; int r; + assert(i); if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) @@ -924,6 +935,118 @@ int image_set_limit(Image *i, uint64_t referenced_max) { return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); } +int image_read_metadata(Image *i) { + _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; + int r; + + assert(i); + + r = image_path_lock(i->path, LOCK_SH|LOCK_NB, &global_lock, &local_lock); + if (r < 0) + return r; + + switch (i->type) { + + case IMAGE_SUBVOLUME: + case IMAGE_DIRECTORY: { + _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL; + sd_id128_t machine_id = SD_ID128_NULL; + _cleanup_free_ char *hostname = NULL; + _cleanup_free_ char *path = NULL; + + r = chase_symlinks("/etc/hostname", i->path, CHASE_PREFIX_ROOT, &path); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to chase /etc/hostname in image %s: %m", i->name); + else if (r >= 0) { + r = read_etc_hostname(path, &hostname); + if (r < 0) + log_debug_errno(errno, "Failed to read /etc/hostname of image %s: %m", i->name); + } + + path = mfree(path); + + r = chase_symlinks("/etc/machine-id", i->path, CHASE_PREFIX_ROOT, &path); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to chase /etc/machine-id in image %s: %m", i->name); + else if (r >= 0) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + log_debug_errno(errno, "Failed to open %s: %m", path); + else { + r = id128_read_fd(fd, ID128_PLAIN, &machine_id); + if (r < 0) + log_debug_errno(r, "Image %s contains invalid machine ID.", i->name); + } + } + + path = mfree(path); + + r = chase_symlinks("/etc/machine-info", i->path, CHASE_PREFIX_ROOT, &path); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to chase /etc/machine-info in image %s: %m", i->name); + else if (r >= 0) { + r = load_env_file_pairs(NULL, path, NULL, &machine_info); + if (r < 0) + log_debug_errno(r, "Failed to parse machine-info data of %s: %m", i->name); + } + + path = mfree(path); + + r = chase_symlinks("/etc/os-release", i->path, CHASE_PREFIX_ROOT, &path); + if (r == -ENOENT) + r = chase_symlinks("/usr/lib/os-release", i->path, CHASE_PREFIX_ROOT, &path); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to chase os-release in image: %m"); + else if (r >= 0) { + r = load_env_file_pairs(NULL, path, NULL, &os_release); + if (r < 0) + log_debug_errno(r, "Failed to parse os-release data of %s: %m", i->name); + } + + free_and_replace(i->hostname, hostname); + i->machine_id = machine_id; + strv_free_and_replace(i->machine_info, machine_info); + strv_free_and_replace(i->os_release, os_release); + + break; + } + + case IMAGE_RAW: + case IMAGE_BLOCK: { + _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; + _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; + + r = loop_device_make_by_path(i->path, O_RDONLY, &d); + if (r < 0) + return r; + + r = dissect_image(d->fd, NULL, 0, DISSECT_IMAGE_REQUIRE_ROOT, &m); + if (r < 0) + return r; + + r = dissected_image_acquire_metadata(m); + if (r < 0) + return r; + + free_and_replace(i->hostname, m->hostname); + i->machine_id = m->machine_id; + strv_free_and_replace(i->machine_info, m->machine_info); + strv_free_and_replace(i->os_release, m->os_release); + + break; + } + + default: + return -EOPNOTSUPP; + } + + i->metadata_valid = true; + + return 0; +} + int image_name_lock(const char *name, int operation, LockFile *ret) { const char *p; diff --git a/src/shared/machine-image.h b/src/shared/machine-image.h index 1b1194e442..9573000a5a 100644 --- a/src/shared/machine-image.h +++ b/src/shared/machine-image.h @@ -53,6 +53,13 @@ typedef struct Image { uint64_t limit; uint64_t limit_exclusive; + char *hostname; + sd_id128_t machine_id; + char **machine_info; + char **os_release; + + bool metadata_valid; + void *userdata; } Image; @@ -80,6 +87,8 @@ int image_name_lock(const char *name, int operation, LockFile *ret); int image_set_limit(Image *i, uint64_t referenced_max); +int image_read_metadata(Image *i); + static inline bool IMAGE_IS_HIDDEN(const struct Image *i) { assert(i); diff --git a/src/test/test-hostname-util.c b/src/test/test-hostname-util.c index d5a986b1cb..b73468ddbc 100644 --- a/src/test/test-hostname-util.c +++ b/src/test/test-hostname-util.c @@ -100,7 +100,7 @@ static void test_hostname_cleanup(void) { assert_se(streq(hostname_cleanup(s), "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")); } -static void test_read_hostname_config(void) { +static void test_read_etc_hostname(void) { char path[] = "/tmp/hostname.XXXXXX"; char *hostname; int fd; @@ -111,27 +111,27 @@ static void test_read_hostname_config(void) { /* simple hostname */ write_string_file(path, "foo", WRITE_STRING_FILE_CREATE); - assert_se(read_hostname_config(path, &hostname) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); assert_se(streq(hostname, "foo")); hostname = mfree(hostname); /* with comment */ write_string_file(path, "# comment\nfoo", WRITE_STRING_FILE_CREATE); - assert_se(read_hostname_config(path, &hostname) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); assert_se(hostname); assert_se(streq(hostname, "foo")); hostname = mfree(hostname); /* with comment and extra whitespace */ write_string_file(path, "# comment\n\n foo ", WRITE_STRING_FILE_CREATE); - assert_se(read_hostname_config(path, &hostname) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); assert_se(hostname); assert_se(streq(hostname, "foo")); hostname = mfree(hostname); /* cleans up name */ write_string_file(path, "!foo/bar.com", WRITE_STRING_FILE_CREATE); - assert_se(read_hostname_config(path, &hostname) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); assert_se(hostname); assert_se(streq(hostname, "foobar.com")); hostname = mfree(hostname); @@ -139,11 +139,11 @@ static void test_read_hostname_config(void) { /* no value set */ hostname = (char*) 0x1234; write_string_file(path, "# nothing here\n", WRITE_STRING_FILE_CREATE); - assert_se(read_hostname_config(path, &hostname) == -ENOENT); + assert_se(read_etc_hostname(path, &hostname) == -ENOENT); assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */ /* nonexisting file */ - assert_se(read_hostname_config("/non/existing", &hostname) == -ENOENT); + assert_se(read_etc_hostname("/non/existing", &hostname) == -ENOENT); assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */ unlink(path); @@ -155,7 +155,7 @@ int main(int argc, char *argv[]) { test_hostname_is_valid(); test_hostname_cleanup(); - test_read_hostname_config(); + test_read_etc_hostname(); return 0; }