add new portable service framework

This adds a small service "systemd-portabled" and a matching client
"portablectl", which implement the "portable service" concept.

The daemon implements the actual operations, is PolicyKit-enabled and is
activated on demand with exit-on-idle.

Both the daemon and the client are an optional build artifact, enabled
by default rhough.
This commit is contained in:
Lennart Poettering 2018-04-16 21:41:40 +02:00
parent 19017acb9f
commit 61d0578b07
28 changed files with 4473 additions and 0 deletions

View File

@ -138,6 +138,7 @@ testsdir = join_paths(prefixdir, 'lib/systemd/tests')
systemdstatedir = join_paths(localstatedir, 'lib/systemd')
catalogstatedir = join_paths(systemdstatedir, 'catalog')
randomseeddir = join_paths(localstatedir, 'lib/systemd')
profiledir = join_paths(rootlibexecdir, 'portable', 'profile')
docdir = get_option('docdir')
if docdir == ''
@ -1177,6 +1178,7 @@ foreach term : ['utmp',
'hostnamed',
'localed',
'machined',
'portabled',
'networkd',
'timedated',
'timesyncd',
@ -1355,6 +1357,7 @@ subdir('src/import')
subdir('src/kernel-install')
subdir('src/locale')
subdir('src/machine')
subdir('src/portable')
subdir('src/nspawn')
subdir('src/resolve')
subdir('src/timedate')
@ -1716,6 +1719,26 @@ exe = executable('systemctl', 'src/systemctl/systemctl.c',
install_dir : rootbindir)
public_programs += [exe]
if conf.get('ENABLE_PORTABLED') == 1
executable('systemd-portabled',
systemd_portabled_sources,
include_directories : includes,
link_with : [libshared],
dependencies : [threads],
install_rpath : rootlibexecdir,
install : true,
install_dir : rootlibexecdir)
exe = executable('portablectl', 'src/portable/portablectl.c',
include_directories : includes,
link_with : [libshared],
dependencies : [threads],
install_rpath : rootlibexecdir,
install : true,
install_dir : rootlibexecdir)
public_programs += [exe]
endif
foreach alias : ['halt', 'poweroff', 'reboot', 'runlevel', 'shutdown', 'telinit']
meson.add_install_script(meson_make_symlink,
join_paths(rootbindir, 'systemctl'),
@ -2895,6 +2918,7 @@ foreach tuple : [
['rfkill'],
['logind'],
['machined'],
['portabled'],
['importd'],
['hostnamed'],
['timedated'],

View File

@ -79,6 +79,8 @@ option('localed', type : 'boolean',
description : 'install the systemd-localed stack')
option('machined', type : 'boolean',
description : 'install the systemd-machined stack')
option('portabled', type : 'boolean',
description : 'install the systemd-portabled stack')
option('networkd', type : 'boolean',
description : 'install the systemd-networkd stack')
option('timedated', type : 'boolean',

View File

@ -41,6 +41,8 @@
#define BUS_ERROR_NO_SUCH_USER_MAPPING "org.freedesktop.machine1.NoSuchUserMapping"
#define BUS_ERROR_NO_SUCH_GROUP_MAPPING "org.freedesktop.machine1.NoSuchGroupMapping"
#define BUS_ERROR_NO_SUCH_PORTABLE_IMAGE "org.freedesktop.portable1.NoSuchImage"
#define BUS_ERROR_NO_SUCH_SESSION "org.freedesktop.login1.NoSuchSession"
#define BUS_ERROR_NO_SESSION_FOR_PID "org.freedesktop.login1.NoSessionForPID"
#define BUS_ERROR_NO_SUCH_USER "org.freedesktop.login1.NoSuchUser"

29
src/portable/meson.build Normal file
View File

@ -0,0 +1,29 @@
# SPDX-License-Identifier: LGPL-2.1+
systemd_portabled_sources = files('''
portable.c
portable.h
portabled-bus.c
portabled-image-bus.c
portabled-image-bus.h
portabled-image.c
portabled-image.h
portabled-operation.c
portabled-operation.h
portabled.c
portabled.h
'''.split())
if conf.get('ENABLE_PORTABLED') == 1
install_data('org.freedesktop.portable1.conf',
install_dir : dbuspolicydir)
install_data('org.freedesktop.portable1.service',
install_dir : dbussystemservicedir)
install_data('org.freedesktop.portable1.policy',
install_dir : polkitpolicydir)
install_data('profile/default/service.conf', install_dir : join_paths(profiledir, 'default'))
install_data('profile/nonetwork/service.conf', install_dir : join_paths(profiledir, 'nonetwork'))
install_data('profile/strict/service.conf', install_dir : join_paths(profiledir, 'strict'))
install_data('profile/trusted/service.conf', install_dir : join_paths(profiledir, 'trusted'))
endif

View File

@ -0,0 +1,117 @@
<?xml version="1.0"?> <!--*-nxml-*-->
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
<busconfig>
<policy user="root">
<allow own="org.freedesktop.portable1"/>
<allow send_destination="org.freedesktop.portable1"/>
<allow receive_sender="org.freedesktop.portable1"/>
</policy>
<policy context="default">
<deny send_destination="org.freedesktop.portable1"/>
<!-- generic interfaces -->
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.DBus.Introspectable"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.DBus.Peer"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.DBus.Properties"
send_member="Get"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.DBus.Properties"
send_member="GetAll"/>
<!-- Manager object -->
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="GetImage"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="ListImages"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="GetImageOSRelease"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="GetImageUnitFiles"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="GetImageState"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="AttachImage"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="DetachImage"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="RemoveImage"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="MarkImageReadOnly"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="SetImageLimit"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Manager"
send_member="SetPoolLimit"/>
<!-- Image object -->
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="GetOSRelease"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="GetUnitFiles"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="GetImageState"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="Attach"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="Detach"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="Remove"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="MarkReadOnly"/>
<allow send_destination="org.freedesktop.portable1"
send_interface="org.freedesktop.portable1.Image"
send_member="SetLimit"/>
<allow receive_sender="org.freedesktop.portable1"/>
</policy>
</busconfig>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
<policyconfig>
<vendor>The systemd Project</vendor>
<vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
<action id="org.freedesktop.portable1.inspect-images">
<description gettext-domain="systemd">Inspect a portable service</description>
<message gettext-domain="systemd">Authentication is required to inspect a portable service.</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.freedesktop.portable1.attach-images">
<description gettext-domain="systemd">Attach or detach a portable service</description>
<message gettext-domain="systemd">Authentication is required to attach or detach a portable service.</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.imply">org.freedesktop.systemd1.reload-daemon</annotate>
</action>
<action id="org.freedesktop.portable1.manage-images">
<description gettext-domain="systemd">Delete or modify portable service image</description>
<message gettext-domain="systemd">Authentication is required to delete or modify a portable service image.</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>

View File

@ -0,0 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1+
[D-BUS Service]
Name=org.freedesktop.portable1
Exec=/bin/false
User=root
SystemdService=dbus-org.freedesktop.portable1.service

1427
src/portable/portable.c Normal file

File diff suppressed because it is too large Load Diff

77
src/portable/portable.h Normal file
View File

@ -0,0 +1,77 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "sd-bus.h"
#include "hashmap.h"
#include "macro.h"
#include "set.h"
#include "string-util.h"
typedef struct PortableMetadata {
int fd;
char *source;
char name[];
} PortableMetadata;
#define PORTABLE_METADATA_IS_OS_RELEASE(m) (streq((m)->name, "/etc/os-release"))
#define PORTABLE_METADATA_IS_UNIT(m) (!IN_SET((m)->name[0], 0, '/'))
typedef enum PortableFlags {
PORTABLE_PREFER_COPY = 1U << 0,
PORTABLE_PREFER_SYMLINK = 1U << 1,
PORTABLE_RUNTIME = 1U << 2,
} PortableFlags;
typedef enum PortableChangeType {
PORTABLE_COPY,
PORTABLE_SYMLINK,
PORTABLE_UNLINK,
PORTABLE_WRITE,
PORTABLE_MKDIR,
_PORTABLE_CHANGE_TYPE_MAX,
_PORTABLE_CHANGE_TYPE_INVALID = INT_MIN,
} PortableChangeType;
typedef enum PortableState {
PORTABLE_DETACHED,
PORTABLE_ATTACHED,
PORTABLE_ATTACHED_RUNTIME,
PORTABLE_ENABLED,
PORTABLE_ENABLED_RUNTIME,
PORTABLE_RUNNING,
PORTABLE_RUNNING_RUNTIME,
_PORTABLE_STATE_MAX,
_PORTABLE_STATE_INVALID = -1
} PortableState;
typedef struct PortableChange {
int type; /* PortableFileChangeType or negative error number */
char *path;
char *source;
} PortableChange;
PortableMetadata *portable_metadata_unref(PortableMetadata *i);
DEFINE_TRIVIAL_CLEANUP_FUNC(PortableMetadata*, portable_metadata_unref);
Hashmap *portable_metadata_hashmap_unref(Hashmap *h);
DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, portable_metadata_hashmap_unref);
int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret);
int portable_extract(const char *image, char **matches, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, sd_bus_error *error);
int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_detach(sd_bus *bus, const char *name_or_path, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error);
int portable_get_state(sd_bus *bus, const char *name_or_path, PortableFlags flags, PortableState *ret, sd_bus_error *error);
int portable_get_profiles(char ***ret);
void portable_changes_free(PortableChange *changes, size_t n_changes);
const char *portable_change_type_to_string(PortableChangeType t) _const_;
PortableChangeType portable_change_type_from_string(const char *t) _pure_;
const char *portable_state_to_string(PortableState t) _const_;
PortableState portable_state_from_string(const char *t) _pure_;

965
src/portable/portablectl.c Normal file
View File

@ -0,0 +1,965 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <errno.h>
#include <getopt.h>
#include "sd-bus.h"
#include "alloc-util.h"
#include "bus-error.h"
#include "bus-util.h"
#include "def.h"
#include "dirent-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "fs-util.h"
#include "locale-util.h"
#include "machine-image.h"
#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
#include "spawn-polkit-agent.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "verbs.h"
static bool arg_no_pager = false;
static bool arg_legend = true;
static bool arg_ask_password = true;
static bool arg_quiet = false;
static const char *arg_profile = "default";
static const char* arg_copy_mode = NULL;
static bool arg_runtime = false;
static bool arg_reload = true;
static bool arg_cat = false;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
static char *arg_host = NULL;
static int determine_image(const char *image, bool permit_non_existing, char **ret) {
int r;
/* If the specified name is a valid image name, we pass it as-is to portabled, which will search for it in the
* usual search directories. Otherwise we presume it's a path, and will normalize it on the client's side
* (among other things, to make the path independent of the client's working directory) before passing it
* over. */
if (image_name_is_valid(image)) {
char *c;
if (!arg_quiet && laccess(image, F_OK) >= 0)
log_warning("Ambiguous invocation: current working directory contains file matching non-path argument '%s', ignoring. "
"Prefix argument with './' to force reference to file in current working directory.", image);
c = strdup(image);
if (!c)
return log_oom();
*ret = c;
return 0;
}
if (arg_transport != BUS_TRANSPORT_LOCAL) {
log_error("Operations on images by path not supported when connecting to remote systems.");
return -EOPNOTSUPP;
}
r = chase_symlinks(image, NULL, CHASE_TRAIL_SLASH | (permit_non_existing ? CHASE_NONEXISTENT : 0), ret);
if (r < 0)
return log_error_errno(r, "Cannot normalize specified image path '%s': %m", image);
return 0;
}
static int extract_prefix(const char *path, char **ret) {
_cleanup_free_ char *name = NULL;
const char *bn, *underscore;
size_t m;
bn = basename(path);
underscore = strchr(bn, '_');
if (underscore)
m = underscore - bn;
else {
const char *e;
e = endswith(bn, ".raw");
if (!e)
e = strchr(bn, 0);
m = e - bn;
}
name = strndup(bn, m);
if (!name)
return -ENOMEM;
/* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_'
* which we use as delimiter for the second part of the image string, which we ignore for now. */
if (!in_charset(name, DIGITS LETTERS "-."))
return -EINVAL;
if (!filename_is_valid(name))
return -EINVAL;
*ret = name;
name = NULL;
return 0;
}
static int determine_matches(const char *image, char **l, bool allow_any, char ***ret) {
char **k;
int r;
/* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list
* contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly
* permitted. */
if (strv_isempty(l)) {
char *prefix;
r = extract_prefix(image, &prefix);
if (r < 0)
return log_error_errno(r, "Failed to extract prefix of image name '%s': %m", image);
if (!arg_quiet)
log_info("(Matching unit files with prefix '%s'.)", prefix);
k = NULL;
r = strv_consume(&k, prefix);
if (r < 0)
return log_oom();
} else if (strv_equal(l, STRV_MAKE("-"))) {
if (!allow_any) {
log_error("Refusing all unit file match.");
return -EINVAL;
}
if (!arg_quiet)
log_info("(Matching all unit files.)");
k = NULL;
} else {
_cleanup_free_ char *joined = NULL;
k = strv_copy(l);
if (!k)
return log_oom();
joined = strv_join(k, "', '");
if (!joined)
return log_oom();
if (!arg_quiet)
log_info("(Matching unit files with prefixes '%s'.)", joined);
}
*ret = k;
return 0;
}
static int acquire_bus(sd_bus **bus) {
int r;
assert(bus);
if (*bus)
return 0;
r = bus_connect_transport(arg_transport, arg_host, false, bus);
if (r < 0)
return log_error_errno(r, "Failed to connect to bus: %m");
(void) sd_bus_set_allow_interactive_authorization(*bus, arg_ask_password);
return 0;
}
static int maybe_reload(sd_bus **bus) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
int r;
if (!arg_reload)
return 0;
r = acquire_bus(bus);
if (r < 0)
return r;
r = sd_bus_message_new_method_call(
*bus,
&m,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"Reload");
if (r < 0)
return bus_log_create_error(r);
/* Reloading the daemon may take long, hence set a longer timeout here */
r = sd_bus_call(*bus, m, DEFAULT_TIMEOUT_USEC * 2, &error, NULL);
if (r < 0)
return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
return 0;
}
static int inspect_image(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_strv_free_ char **matches = NULL;
_cleanup_free_ char *image = NULL;
bool nl = false, header = false;
const void *data;
const char *path;
size_t sz;
int r;
r = determine_image(argv[1], false, &image);
if (r < 0)
return r;
r = determine_matches(argv[1], argv + 2, true, &matches);
if (r < 0)
return r;
r = acquire_bus(&bus);
if (r < 0)
return r;
r = sd_bus_message_new_method_call(
bus,
&m,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"GetImageMetadata");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", image);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, matches);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, &reply);
if (r < 0)
return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r));
r = sd_bus_message_read(reply, "s", &path);
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_read_array(reply, 'y', &data, &sz);
if (r < 0)
return bus_log_parse_error(r);
(void) pager_open(arg_no_pager, false);
if (arg_cat) {
printf("%s-- OS Release: --%s\n", ansi_highlight(), ansi_normal());
fwrite(data, sz, 1, stdout);
fflush(stdout);
nl = true;
} else {
const char *pretty_portable = NULL, *pretty_os = NULL;
_cleanup_fclose_ FILE *f;
f = fmemopen((void*) data, sz, "re");
if (!f)
return log_error_errno(errno, "Failed to open /etc/os-release buffer: %m");
r = parse_env_file(f, "/etc/os-release", NEWLINE,
"PORTABLE_PRETTY_NAME", &pretty_portable,
"PRETTY_NAME", &pretty_os,
NULL);
if (r < 0)
return log_error_errno(r, "Failed to parse /etc/os-release: %m");
printf("Image:\n\t%s\n"
"Portable Service:\n\t%s\n"
"Operating System:\n\t%s\n",
path,
strna(pretty_portable),
strna(pretty_os));
}
r = sd_bus_message_enter_container(reply, 'a', "{say}");
if (r < 0)
return bus_log_parse_error(r);
for (;;) {
const char *name;
r = sd_bus_message_enter_container(reply, 'e', "say");
if (r < 0)
return bus_log_parse_error(r);
if (r == 0)
break;
r = sd_bus_message_read(reply, "s", &name);
if (r < 0)
return bus_log_parse_error(r);
r = sd_bus_message_read_array(reply, 'y', &data, &sz);
if (r < 0)
return bus_log_parse_error(r);
if (arg_cat) {
if (nl)
fputc('\n', stdout);
printf("%s-- Unit file: %s --%s\n", ansi_highlight(), name, ansi_normal());
fwrite(data, sz, 1, stdout);
fflush(stdout);
nl = true;
} else {
if (!header) {
fputs("Unit files:\n", stdout);
header = true;
}
fputc('\t', stdout);
fputs(name, stdout);
fputc('\n', stdout);
}
r = sd_bus_message_exit_container(reply);
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);
return 0;
}
static int print_changes(sd_bus_message *m) {
int r;
if (arg_quiet)
return 0;
r = sd_bus_message_enter_container(m, 'a', "(sss)");
if (r < 0)
return bus_log_parse_error(r);
for (;;) {
const char *type, *path, *source;
r = sd_bus_message_read(m, "(sss)", &type, &path, &source);
if (r < 0)
return bus_log_parse_error(r);
if (r == 0)
break;
if (streq(type, "symlink"))
log_info("Created symlink %s %s %s.", path, special_glyph(ARROW), source);
else if (streq(type, "copy")) {
if (isempty(source))
log_info("Copied %s.", path);
else
log_info("Copied %s %s %s.", source, special_glyph(ARROW), path);
} else if (streq(type, "unlink"))
log_info("Removed %s.", path);
else if (streq(type, "write"))
log_info("Written %s.", path);
else if (streq(type, "mkdir"))
log_info("Created directory %s.", path);
else
log_error("Unexpected change: %s/%s/%s", type, path, source);
}
r = sd_bus_message_exit_container(m);
if (r < 0)
return r;
return 0;
}
static int attach_image(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_strv_free_ char **matches = NULL;
_cleanup_free_ char *image = NULL;
int r;
r = determine_image(argv[1], false, &image);
if (r < 0)
return r;
r = determine_matches(argv[1], argv + 2, false, &matches);
if (r < 0)
return r;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = sd_bus_message_new_method_call(
bus,
&m,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"AttachImage");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", image);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, matches);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "sbs", arg_profile, arg_runtime, arg_copy_mode);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, &reply);
if (r < 0)
return log_error_errno(r, "Failed to attach image: %s", bus_error_message(&error, r));
(void) maybe_reload(&bus);
print_changes(reply);
return 0;
}
static int detach_image(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *image = NULL;
int r;
r = determine_image(argv[1], true, &image);
if (r < 0)
return r;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"DetachImage",
&error,
&reply,
"sb", image, arg_runtime);
if (r < 0)
return log_error_errno(r, "Failed to detach image: %s", bus_error_message(&error, r));
(void) maybe_reload(&bus);
print_changes(reply);
return 0;
}
static int list_images(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(table_unrefp) Table *table = NULL;
int r;
r = acquire_bus(&bus);
if (r < 0)
return r;
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"ListImages",
&error,
&reply,
NULL);
if (r < 0)
return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r));
table = table_new("NAME", "TYPE", "RO", "CRTIME", "MTIME", "USAGE", "STATE");
if (!table)
return log_oom();
r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)");
if (r < 0)
return bus_log_parse_error(r);
for (;;) {
const char *name, *type, *state, *object;
uint64_t crtime, mtime, usage;
TableCell *cell;
bool ro_bool;
int ro_int;
r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, &object);
if (r < 0)
return bus_log_parse_error(r);
if (r == 0)
break;
r = table_add_many(table,
TABLE_STRING, name,
TABLE_STRING, type);
if (r < 0)
return log_error_errno(r, "Failed to add row to table: %m");
ro_bool = ro_int;
r = table_add_cell(table, &cell, TABLE_BOOLEAN, &ro_bool);
if (r < 0)
return log_error_errno(r, "Failed to add row to table: %m");
if (ro_bool) {
r = table_set_color(table, cell, ansi_highlight_red());
if (r < 0)
return log_error_errno(r, "Failed to set table cell color: %m");
}
r = table_add_many(table,
TABLE_TIMESTAMP, crtime,
TABLE_TIMESTAMP, mtime,
TABLE_SIZE, usage);
if (r < 0)
return log_error_errno(r, "Failed to add row to table: %m");
r = table_add_cell(table, &cell, TABLE_STRING, state);
if (r < 0)
return log_error_errno(r, "Failed to add row to table: %m");
if (!streq(state, "detached")) {
r = table_set_color(table, cell, ansi_highlight_green());
if (r < 0)
return log_error_errno(r, "Failed to set table cell color: %m");
}
}
r = sd_bus_message_exit_container(reply);
if (r < 0)
return bus_log_parse_error(r);
if (table_get_rows(table) > 1) {
r = table_set_sort(table, (size_t) 0, (size_t) -1);
if (r < 0)
return log_error_errno(r, "Failed to sort table: %m");
table_set_header(table, arg_legend);
r = table_print(table, NULL);
if (r < 0)
return log_error_errno(r, "Failed to show table: %m");
}
if (arg_legend) {
if (table_get_rows(table) > 1)
printf("\n%zu images listed.\n", table_get_rows(table) - 1);
else
printf("No images.\n");
}
return 0;
}
static int remove_image(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r, i;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
for (i = 1; i < argc; i++) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
r = sd_bus_message_new_method_call(
bus,
&m,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"RemoveImage");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", argv[i]);
if (r < 0)
return bus_log_create_error(r);
/* This is a slow operation, hence turn off any method call timeouts */
r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
if (r < 0)
return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
}
return 0;
}
static int read_only_image(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int b = true, r;
if (argc > 2) {
b = parse_boolean(argv[2]);
if (b < 0)
return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]);
}
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"MarkImageReadOnly",
&error,
NULL,
"sb", argv[1], b);
if (r < 0)
return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
return 0;
}
static int set_limit(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
uint64_t limit;
int r;
r = acquire_bus(&bus);
if (r < 0)
return r;
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
if (STR_IN_SET(argv[argc-1], "-", "none", "infinity"))
limit = (uint64_t) -1;
else {
r = parse_size(argv[argc-1], 1024, &limit);
if (r < 0)
return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]);
}
if (argc > 2)
/* With two arguments changes the quota limit of the specified image */
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"SetImageLimit",
&error,
NULL,
"st", argv[1], limit);
else
/* With one argument changes the pool quota limit */
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"SetPoolLimit",
&error,
NULL,
"t", limit);
if (r < 0)
return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r));
return 0;
}
static int is_image_attached(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *image = NULL;
const char *state;
int r;
r = determine_image(argv[1], true, &image);
if (r < 0)
return r;
r = acquire_bus(&bus);
if (r < 0)
return r;
r = sd_bus_call_method(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"GetImageState",
&error,
&reply,
"s", image);
if (r < 0)
return log_error_errno(r, "Failed to get image state: %s", bus_error_message(&error, r));
r = sd_bus_message_read(reply, "s", &state);
if (r < 0)
return r;
if (!arg_quiet)
puts(state);
return streq(state, "detached");
}
static int dump_profiles(void) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_strv_free_ char **l = NULL;
_cleanup_(closedirp) DIR *d = NULL;
char **i;
int r;
r = acquire_bus(&bus);
if (r < 0)
return r;
r = sd_bus_get_property_strv(
bus,
"org.freedesktop.portable1",
"/org/freedesktop/portable1",
"org.freedesktop.portable1.Manager",
"Profiles",
&error,
&l);
if (r < 0)
return log_error_errno(r, "Failed to acquire list of profiles: %s", bus_error_message(&error, r));
if (arg_legend)
log_info("Available unit profiles:");
STRV_FOREACH(i, l) {
fputs(*i, stdout);
fputc('\n', stdout);
}
return 0;
}
static int help(int argc, char *argv[], void *userdata) {
(void) pager_open(arg_no_pager, false);
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
"Attach or detach portable services from the local system.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not show the headers and footers\n"
" --no-ask-password Do not ask for system passwords\n"
" -H --host=[USER@]HOST Operate on remote host\n"
" -M --machine=CONTAINER Operate on local container\n"
" -q --quiet Suppress informational messages\n"
" -p --profile=PROFILE Pick security profile for portable service\n"
" --copy=copy|auto|symlink Prefer copying or symlinks if possible\n"
" --runtime Attach portable service until next reboot only\n"
" --no-reload Don't reload the system and service manager\n"
" --cat When inspecting include unit and os-release file\n"
" contents\n\n"
"Commands:\n"
" list List available portable service images\n"
" attach NAME|PATH [PREFIX...]\n"
" Attach the specified portable service image\n"
" detach NAME|PATH Detach the specified portable service image\n"
" inspect NAME|PATH [PREFIX...]\n"
" Show details of specified portable service image\n"
" is-attached NAME|PATH Query if portable service image is attached\n"
" read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n"
" remove NAME|PATH... Remove a portable service image\n"
" set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n"
, program_invocation_short_name);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_NO_PAGER,
ARG_NO_LEGEND,
ARG_NO_ASK_PASSWORD,
ARG_COPY,
ARG_RUNTIME,
ARG_NO_RELOAD,
ARG_CAT,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "quiet", no_argument, NULL, 'q' },
{ "profile", required_argument, NULL, 'p' },
{ "copy", required_argument, NULL, ARG_COPY },
{ "runtime", no_argument, NULL, ARG_RUNTIME },
{ "no-reload", no_argument, NULL, ARG_NO_RELOAD },
{ "cat", no_argument, NULL, ARG_CAT },
{}
};
assert(argc >= 0);
assert(argv);
for (;;) {
int c;
c = getopt_long(argc, argv, "hH:M:qp:", options, NULL);
if (c < 0)
break;
switch (c) {
case 'h':
help(0, NULL, NULL);
return 0;
case ARG_VERSION:
return version();
case ARG_NO_PAGER:
arg_no_pager = true;
break;
case ARG_NO_LEGEND:
arg_legend = false;
break;
case ARG_NO_ASK_PASSWORD:
arg_ask_password = false;
break;
case 'H':
arg_transport = BUS_TRANSPORT_REMOTE;
arg_host = optarg;
break;
case 'M':
arg_transport = BUS_TRANSPORT_MACHINE;
arg_host = optarg;
break;
case 'q':
arg_quiet = true;
break;
case 'p':
if (!filename_is_valid(optarg)) {
log_error("Unit profile name not valid: %s", optarg);
return -EINVAL;
}
if (streq(optarg, "help"))
return dump_profiles();
arg_profile = optarg;
break;
case ARG_COPY:
if (streq(optarg, "auto"))
arg_copy_mode = NULL;
else if (STR_IN_SET(optarg, "copy", "symlink"))
arg_copy_mode = optarg;
else {
log_error("Failed to parse --copy= argument: %s", optarg);
return -EINVAL;
}
break;
case ARG_RUNTIME:
arg_runtime = true;
break;
case ARG_NO_RELOAD:
arg_reload = false;
break;
case ARG_CAT:
arg_cat = true;
break;
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
}
return 1;
}
int main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "list", VERB_ANY, 1, VERB_DEFAULT, list_images },
{ "attach", 2, VERB_ANY, 0, attach_image },
{ "detach", 2, 2, 0, detach_image },
{ "inspect", 2, VERB_ANY, 0, inspect_image },
{ "is-attached", 2, 2, 0, is_image_attached },
{ "read-only", 2, 3, 0, read_only_image },
{ "remove", 2, VERB_ANY, 0, remove_image },
{ "set-limit", 3, 3, 0, set_limit },
{}
};
int r;
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r <= 0)
goto finish;
r = dispatch_verb(argc, argv, verbs, NULL);
finish:
pager_close();
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@ -0,0 +1,402 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "btrfs-util.h"
#include "bus-common-errors.h"
#include "bus-util.h"
#include "fd-util.h"
#include "io-util.h"
#include "machine-image.h"
#include "portable.h"
#include "portabled-bus.h"
#include "portabled-image-bus.h"
#include "portabled-image.h"
#include "portabled.h"
#include "strv.h"
#include "user-util.h"
static int property_get_pool_path(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
assert(bus);
assert(reply);
return sd_bus_message_append(reply, "s", "/var/lib/portables");
}
static int property_get_pool_usage(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
_cleanup_close_ int fd = -1;
uint64_t usage = (uint64_t) -1;
assert(bus);
assert(reply);
fd = open("/var/lib/portables", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
if (fd >= 0) {
BtrfsQuotaInfo q;
if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0)
usage = q.referenced;
}
return sd_bus_message_append(reply, "t", usage);
}
static int property_get_pool_limit(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
_cleanup_close_ int fd = -1;
uint64_t size = (uint64_t) -1;
assert(bus);
assert(reply);
fd = open("/var/lib/portables", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
if (fd >= 0) {
BtrfsQuotaInfo q;
if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0)
size = q.referenced_max;
}
return sd_bus_message_append(reply, "t", size);
}
static int property_get_profiles(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
_cleanup_strv_free_ char **l = NULL;
int r;
assert(bus);
assert(reply);
r = portable_get_profiles(&l);
if (r < 0)
return r;
return sd_bus_message_append_strv(reply, l);
}
static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_free_ char *p = NULL;
Manager *m = userdata;
const char *name;
Image *image;
int r;
assert(message);
assert(m);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
r = bus_image_acquire(m, message, name, NULL, BUS_IMAGE_REFUSE_BY_PATH, NULL, &image, error);
if (r < 0)
return r;
r = bus_image_path(image, &p);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, "o", p);
}
static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(image_hashmap_freep) Hashmap *images = NULL;
Manager *m = userdata;
Image *image;
Iterator i;
int r;
assert(message);
assert(m);
images = hashmap_new(&string_hash_ops);
if (!images)
return -ENOMEM;
r = manager_image_cache_discover(m, images, error);
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', "(ssbtttso)");
if (r < 0)
return r;
HASHMAP_FOREACH(image, images, i) {
_cleanup_(sd_bus_error_free) sd_bus_error error_state = SD_BUS_ERROR_NULL;
PortableState state = _PORTABLE_STATE_INVALID;
_cleanup_free_ char *p = NULL;
r = bus_image_path(image, &p);
if (r < 0)
return r;
r = portable_get_state(
sd_bus_message_get_bus(message),
image->path,
0,
&state,
&error_state);
if (r < 0)
log_debug_errno(r, "Failed to get state of image '%s', ignoring: %s",
image->path, bus_error_message(&error_state, r));
r = sd_bus_message_append(reply, "(ssbtttso)",
image->name,
image_type_to_string(image->type),
image->read_only,
image->crtime,
image->mtime,
image->usage,
portable_state_to_string(state),
p);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}
static int redirect_method_to_image(
Manager *m,
sd_bus_message *message,
sd_bus_error *error,
int (*method)(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error* error)) {
const char *name_or_path;
int r;
assert(m);
assert(message);
assert(method);
r = sd_bus_message_read(message, "s", &name_or_path);
if (r < 0)
return r;
return method(m, message, name_or_path, NULL, error);
}
static int method_get_image_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_get_os_release);
}
static int method_get_image_metadata(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_get_metadata);
}
static int method_get_image_state(sd_bus_message *message, void *userdata, sd_bus_error *error) {
const char *name_or_path;
PortableState state;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &name_or_path);
if (r < 0)
return r;
r = portable_get_state(
sd_bus_message_get_bus(message),
name_or_path,
0,
&state,
error);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, "s", portable_state_to_string(state));
}
static int method_attach_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_attach);
}
static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
PortableChange *changes = NULL;
Manager *m = userdata;
size_t n_changes = 0;
const char *name_or_path;
int r, runtime;
assert(message);
assert(m);
/* Note that we do not redirect detaching to the image object here, because we want to allow that users can
* detach already deleted images too, in case the user already deleted an image before properly detaching
* it. */
r = sd_bus_message_read(message, "sb", &name_or_path, &runtime);
if (r < 0)
return r;
r = bus_verify_polkit_async(
message,
CAP_SYS_ADMIN,
"org.freedesktop.portable1.attach-images",
NULL,
false,
UID_INVALID,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = portable_detach(
sd_bus_message_get_bus(message),
name_or_path,
runtime ? PORTABLE_RUNTIME : 0,
&changes,
&n_changes,
error);
if (r < 0)
goto finish;
r = reply_portable_changes(message, changes, n_changes);
finish:
portable_changes_free(changes, n_changes);
return r;
}
static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_remove);
}
static int method_mark_image_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_mark_read_only);
}
static int method_set_image_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return redirect_method_to_image(userdata, message, error, bus_image_common_set_limit);
}
static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
uint64_t limit;
int r;
assert(message);
r = sd_bus_message_read(message, "t", &limit);
if (r < 0)
return r;
if (!FILE_SIZE_VALID_OR_INFINITY(limit))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
r = bus_verify_polkit_async(
message,
CAP_SYS_ADMIN,
"org.freedesktop.portable1.manage-images",
NULL,
false,
UID_INVALID,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
(void) btrfs_qgroup_set_limit("/var/lib/portables", 0, limit);
r = btrfs_subvol_set_subtree_quota_limit("/var/lib/portables", 0, limit);
if (r == -ENOTTY)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs.");
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to adjust quota limit: %m");
return sd_bus_reply_method_return(message, NULL);
}
const sd_bus_vtable manager_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0),
SD_BUS_PROPERTY("PoolUsage", "t", property_get_pool_usage, 0, 0),
SD_BUS_PROPERTY("PoolLimit", "t", property_get_pool_limit, 0, 0),
SD_BUS_PROPERTY("Profiles", "as", property_get_profiles, 0, 0),
SD_BUS_METHOD("GetImage", "s", "o", method_get_image, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ListImages", NULL, "a(ssbtttso)", method_list_images, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetImageOSRelease", "s", "a{ss}", method_get_image_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetImageState", "s", "s", method_get_image_state, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetImageMetadata", "sas", "saya{say}", method_get_image_metadata, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("AttachImage", "sassbs", "a(sss)", method_attach_image, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("DetachImage", "sb", "a(sss)", method_detach_image, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END
};
int reply_portable_changes(sd_bus_message *m, const PortableChange *changes, size_t n_changes) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
size_t i;
int r;
assert(m);
assert(changes || n_changes == 0);
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "(sss)");
if (r < 0)
return r;
for (i = 0; i < n_changes; i++) {
r = sd_bus_message_append(reply, "(sss)",
portable_change_type_to_string(changes[i].type),
changes[i].path,
changes[i].source);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}

View File

@ -0,0 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "sd-bus.h"
#include "portable.h"
extern const sd_bus_vtable manager_vtable[];
int reply_portable_changes(sd_bus_message *m, const PortableChange *changes, size_t n_changes);

View File

@ -0,0 +1,733 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "bus-common-errors.h"
#include "bus-label.h"
#include "bus-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "io-util.h"
#include "machine-image.h"
#include "portable.h"
#include "portabled-bus.h"
#include "portabled-image-bus.h"
#include "portabled-image.h"
#include "portabled.h"
#include "process-util.h"
#include "strv.h"
#include "user-util.h"
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType);
int bus_image_common_get_os_release(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
int r;
assert(name_or_path || image);
assert(message);
if (!m) {
assert(image);
m = image->userdata;
}
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_BY_PATH,
"org.freedesktop.portable1.inspect-images",
&image,
error);
if (r < 0)
return r;
if (r == 0) /* Will call us back */
return 1;
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->os_release);
}
static int bus_image_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_get_os_release(NULL, message, NULL, userdata, error);
}
static int append_fd(sd_bus_message *m, PortableMetadata *d) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *buf = NULL;
size_t n;
int r;
assert(m);
assert(d);
assert(d->fd >= 0);
f = fdopen(d->fd, "re");
if (!f)
return -errno;
d->fd = -1;
r = read_full_stream(f, &buf, &n);
if (r < 0)
return r;
return sd_bus_message_append_array(m, 'y', buf, n);
}
int bus_image_common_get_metadata(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
_cleanup_(portable_metadata_hashmap_unrefp) Hashmap *unit_files = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ PortableMetadata **sorted = NULL;
_cleanup_strv_free_ char **matches = NULL;
size_t i;
int r;
assert(name_or_path || image);
assert(message);
if (!m) {
assert(image);
m = image->userdata;
}
r = sd_bus_message_read_strv(message, &matches);
if (r < 0)
return r;
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_BY_PATH,
"org.freedesktop.portable1.inspect-images",
&image,
error);
if (r < 0)
return r;
if (r == 0) /* Will call us back */
return 1;
r = portable_extract(
image->path,
matches,
&os_release,
&unit_files,
error);
if (r < 0)
return r;
r = portable_metadata_hashmap_to_sorted_array(unit_files, &sorted);
if (r < 0)
return r;
r = sd_bus_message_new_method_return(message, &reply);
if (r < 0)
return r;
r = sd_bus_message_append(reply, "s", image->path);
if (r < 0)
return r;
r = append_fd(reply, os_release);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "{say}");
if (r < 0)
return r;
for (i = 0; i < hashmap_size(unit_files); i++) {
r = sd_bus_message_open_container(reply, 'e', "say");
if (r < 0)
return r;
r = sd_bus_message_append(reply, "s", sorted[i]->name);
if (r < 0)
return r;
r = append_fd(reply, sorted[i]);
if (r < 0)
return r;
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}
static int bus_image_method_get_metadata(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_get_metadata(NULL, message, NULL, userdata, error);
}
static int bus_image_method_get_state(
sd_bus_message *message,
void *userdata,
sd_bus_error *error) {
Image *image = userdata;
PortableState state;
int r;
assert(message);
assert(image);
r = portable_get_state(
sd_bus_message_get_bus(message),
image->path,
0,
&state,
error);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, "s", portable_state_to_string(state));
}
int bus_image_common_attach(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
_cleanup_free_ char **matches = NULL;
PortableChange *changes = NULL;
PortableFlags flags = 0;
const char *copy_mode;
size_t n_changes = 0;
const char *profile;
int runtime, r;
assert(message);
assert(name_or_path || image);
if (!m) {
assert(image);
m = image->userdata;
}
r = sd_bus_message_read_strv(message, &matches);
if (r < 0)
return r;
r = sd_bus_message_read(message, "sbs", &profile, &runtime, &copy_mode);
if (r < 0)
return r;
if (streq(copy_mode, "symlink"))
flags |= PORTABLE_PREFER_SYMLINK;
else if (streq(copy_mode, "copy"))
flags |= PORTABLE_PREFER_COPY;
else if (!isempty(copy_mode))
return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
if (runtime)
flags |= PORTABLE_RUNTIME;
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_ALL,
"org.freedesktop.portable1.attach-images",
&image,
error);
if (r < 0)
return r;
if (r == 0) /* Will call us back */
return 1;
r = portable_attach(
sd_bus_message_get_bus(message),
image->path,
matches,
profile,
flags,
&changes,
&n_changes,
error);
if (r < 0)
goto finish;
r = reply_portable_changes(message, changes, n_changes);
finish:
portable_changes_free(changes, n_changes);
return r;
}
static int bus_image_method_attach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_attach(NULL, message, NULL, userdata, error);
}
static int bus_image_method_detach(
sd_bus_message *message,
void *userdata,
sd_bus_error *error) {
PortableChange *changes = NULL;
Image *image = userdata;
Manager *m = image->userdata;
size_t n_changes = 0;
int r, runtime;
assert(message);
assert(image);
assert(m);
r = sd_bus_message_read(message, "b", &runtime);
if (r < 0)
return r;
r = bus_verify_polkit_async(
message,
CAP_SYS_ADMIN,
"org.freedesktop.portable1.attach-images",
NULL,
false,
UID_INVALID,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = portable_detach(
sd_bus_message_get_bus(message),
image->path,
runtime ? PORTABLE_RUNTIME : 0,
&changes,
&n_changes,
error);
if (r < 0)
goto finish;
r = reply_portable_changes(message, changes, n_changes);
finish:
portable_changes_free(changes, n_changes);
return r;
}
int bus_image_common_remove(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
_cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
_cleanup_(sigkill_waitp) pid_t child = 0;
PortableState state;
int r;
assert(message);
assert(name_or_path || image);
if (!m) {
assert(image);
m = image->userdata;
}
if (m->n_operations >= OPERATIONS_MAX)
return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_ALL,
"org.freedesktop.portable1.manage-images",
&image,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = portable_get_state(
sd_bus_message_get_bus(message),
image->path,
0,
&state,
error);
if (r < 0)
return r;
if (state != PORTABLE_DETACHED)
return sd_bus_error_set_errnof(error, EBUSY, "Image '%s' is not detached, refusing.", image->path);
if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
r = safe_fork("(sd-imgrm)", FORK_RESET_SIGNALS, &child);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to fork(): %m");
if (r == 0) {
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
r = image_remove(image);
if (r < 0) {
(void) write(errno_pipe_fd[1], &r, sizeof(r));
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
r = operation_new(m, child, message, errno_pipe_fd[0], NULL);
if (r < 0)
return r;
child = 0;
errno_pipe_fd[0] = -1;
return 1;
}
static int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_remove(NULL, message, NULL, userdata, error);
}
int bus_image_common_mark_read_only(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
int r, read_only;
assert(message);
assert(name_or_path || image);
if (!m) {
assert(image);
m = image->userdata;
}
r = sd_bus_message_read(message, "b", &read_only);
if (r < 0)
return r;
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_ALL,
"org.freedesktop.portable1.manage-images",
&image,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = image_read_only(image, read_only);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, NULL);
}
static int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_mark_read_only(NULL, message, NULL, userdata, error);
}
int bus_image_common_set_limit(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
sd_bus_error *error) {
uint64_t limit;
int r;
assert(message);
assert(name_or_path || image);
if (!m) {
assert(image);
m = image->userdata;
}
r = sd_bus_message_read(message, "t", &limit);
if (r < 0)
return r;
if (!FILE_SIZE_VALID_OR_INFINITY(limit))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
r = bus_image_acquire(m,
message,
name_or_path,
image,
BUS_IMAGE_AUTHENTICATE_ALL,
"org.freedesktop.portable1.manage-images",
&image,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
r = image_set_limit(image, limit);
if (r < 0)
return r;
return sd_bus_reply_method_return(message, NULL);
}
static int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return bus_image_common_set_limit(NULL, message, NULL, userdata, error);
}
const sd_bus_vtable image_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0),
SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0),
SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0),
SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0),
SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0),
SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0),
SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0),
SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0),
SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0),
SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0),
SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_image_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetMedatadata", "as", "saya{say}", bus_image_method_get_metadata, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetState", NULL, "s", bus_image_method_get_state, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Attach", "assbs", "a(sss)", bus_image_method_attach, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Detach", "b", "a(sss)", bus_image_method_detach, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, 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_VTABLE_END
};
int bus_image_path(Image *image, char **ret) {
assert(image);
assert(ret);
if (!image->discoverable)
return -EINVAL;
return sd_bus_path_encode("/org/freedesktop/portable1/image", image->name, ret);
}
int bus_image_acquire(
Manager *m,
sd_bus_message *message,
const char *name_or_path,
Image *image,
ImageAcquireMode mode,
const char *polkit_action,
Image **ret,
sd_bus_error *error) {
_cleanup_(image_unrefp) Image *loaded = NULL;
Image *cached;
int r;
assert(m);
assert(message);
assert(name_or_path || image);
assert(mode >= 0);
assert(mode < _BUS_IMAGE_ACQUIRE_MODE_MAX);
assert(polkit_action || mode == BUS_IMAGE_REFUSE_BY_PATH);
assert(ret);
/* Acquires an 'Image' object if not acquired yet, and enforces necessary authentication while doing so. */
if (mode == BUS_IMAGE_AUTHENTICATE_ALL) {
r = bus_verify_polkit_async(
message,
CAP_SYS_ADMIN,
polkit_action,
NULL,
false,
UID_INVALID,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0) { /* Will call us back */
*ret = NULL;
return 0;
}
}
/* Already passed in? */
if (image) {
*ret = image;
return 1;
}
/* Let's see if this image is already cached? */
cached = manager_image_cache_get(m, name_or_path);
if (cached) {
*ret = cached;
return 1;
}
if (image_name_is_valid(name_or_path)) {
/* If it's a short name, let's search for it */
r = image_find(IMAGE_PORTABLE, name_or_path, &loaded);
if (r == -ENOENT)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE, "No image '%s' found.", name_or_path);
/* other errors are handled below… */
} else {
/* Don't accept path if this is always forbidden */
if (mode == BUS_IMAGE_REFUSE_BY_PATH)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Expected image name, not path in place of '%s'.", name_or_path);
if (!path_is_absolute(name_or_path))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is not valid or not a valid path.", name_or_path);
if (!path_is_normalized(name_or_path))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image path '%s' is not normalized.", name_or_path);
if (mode == BUS_IMAGE_AUTHENTICATE_BY_PATH) {
r = bus_verify_polkit_async(
message,
CAP_SYS_ADMIN,
polkit_action,
NULL,
false,
UID_INVALID,
&m->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0) { /* Will call us back */
*ret = NULL;
return 0;
}
}
r = image_from_path(name_or_path, &loaded);
}
if (r < 0)
return r;
/* Add what we just loaded to the cache. This has as side-effect that the object stays in memory until the
* cache is purged again, i.e. at least for the current event loop iteration, which is all we need, and which
* means we don't actually need to ref the return object. */
r = manager_image_cache_add(m, loaded);
if (r < 0)
return r;
*ret = loaded;
return 1;
}
int bus_image_object_find(
sd_bus *bus,
const char *path,
const char *interface,
void *userdata,
void **found,
sd_bus_error *error) {
_cleanup_free_ char *e = NULL;
Manager *m = userdata;
Image *image = NULL;
int r;
assert(bus);
assert(path);
assert(interface);
assert(found);
r = sd_bus_path_decode(path, "/org/freedesktop/portable1/image", &e);
if (r < 0)
return 0;
if (r == 0)
goto not_found;
r = bus_image_acquire(m, sd_bus_get_current_message(bus), e, NULL, BUS_IMAGE_REFUSE_BY_PATH, NULL, &image, error);
if (r == -ENOENT)
goto not_found;
if (r < 0)
return r;
*found = image;
return 1;
not_found:
*found = NULL;
return 0;
}
int bus_image_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;
size_t n_allocated = 0, n = 0;
Manager *m = userdata;
Image *image;
Iterator i;
int r;
assert(bus);
assert(path);
assert(nodes);
images = hashmap_new(&string_hash_ops);
if (!images)
return -ENOMEM;
r = manager_image_cache_discover(m, images, error);
if (r < 0)
return r;
HASHMAP_FOREACH(image, images, i) {
char *p;
r = bus_image_path(image, &p);
if (r < 0)
return r;
if (!GREEDY_REALLOC(l, n_allocated, n+2)) {
free(p);
return -ENOMEM;
}
l[n++] = p;
l[n] = NULL;
}
*nodes = TAKE_PTR(l);
return 1;
}

View File

@ -0,0 +1,40 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "sd-bus.h"
#include "machine-image.h"
#include "portabled.h"
int bus_image_common_get_os_release(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
int bus_image_common_get_metadata(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
int bus_image_common_attach(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
int bus_image_common_remove(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
int bus_image_common_mark_read_only(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
int bus_image_common_set_limit(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, sd_bus_error *error);
extern const sd_bus_vtable image_vtable[];
int bus_image_path(Image *image, char **ret);
/* So here's some complexity: some of operations can either take an image name, or a fully qualified file system path
* to an image. We need to authenticate differently when processing these two: images referenced via simple image names
* mean the images are located in the image search path and thus safe for limited read access for unprivileged
* clients. For operations on images located anywhere else we need explicit authentication however, so that
* unprivileged clients can't make us open arbitrary files in the file system.
*
* The "Image" bus objects directly represent images in the image search path, but do not exist for path-referenced
* images. Hence, when requesting a bus object we need to refuse references by file system path, but still allow
* references by image name. Depending on the operation to execute potentially we need to authenticate in all cases. */
typedef enum ImageAcquireMode {
BUS_IMAGE_REFUSE_BY_PATH, /* allow by name + prohibit by path */
BUS_IMAGE_AUTHENTICATE_BY_PATH, /* allow by name + polkit by path */
BUS_IMAGE_AUTHENTICATE_ALL, /* polkit by name + polkit by path */
_BUS_IMAGE_ACQUIRE_MODE_MAX,
_BUS_IMAGE_ACQUIRE_MODE_INVALID = -1
} ImageAcquireMode;
int bus_image_acquire(Manager *m, sd_bus_message *message, const char *name_or_path, Image *image, ImageAcquireMode mode, const char *polkit_action, Image **ret, sd_bus_error *error);
int bus_image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error);
int bus_image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);

View File

@ -0,0 +1,105 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "portable.h"
#include "portabled-image.h"
#include "portabled.h"
Image *manager_image_cache_get(Manager *m, const char *name_or_path) {
assert(m);
return hashmap_get(m->image_cache, name_or_path);
}
static int image_cache_flush(sd_event_source *s, void *userdata) {
Manager *m = userdata;
assert(s);
assert(m);
hashmap_clear_with_destructor(m->image_cache, image_unref);
return 0;
}
static int manager_image_cache_initialize(Manager *m) {
int r;
assert(m);
r = hashmap_ensure_allocated(&m->image_cache, &string_hash_ops);
if (r < 0)
return r;
/* We flush the cache as soon as we are idle again */
if (!m->image_cache_defer_event) {
r = sd_event_add_defer(m->event, &m->image_cache_defer_event, image_cache_flush, m);
if (r < 0)
return r;
r = sd_event_source_set_priority(m->image_cache_defer_event, SD_EVENT_PRIORITY_IDLE);
if (r < 0)
return r;
}
r = sd_event_source_set_enabled(m->image_cache_defer_event, SD_EVENT_ONESHOT);
if (r < 0)
return r;
return 0;
}
int manager_image_cache_add(Manager *m, Image *image) {
int r;
assert(m);
/* We add the specified image to the cache under two keys.
*
* 1. Always under its path
*
* 2. If the image was discovered in the search path (i.e. its discoverable boolean set) we'll also add it
* under its short name.
*
*/
r = manager_image_cache_initialize(m);
if (r < 0)
return r;
image->userdata = m;
r = hashmap_put(m->image_cache, image->path, image);
if (r < 0)
return r;
image_ref(image);
if (image->discoverable) {
r = hashmap_put(m->image_cache, image->name, image);
if (r < 0)
return r;
image_ref(image);
}
return 0;
}
int manager_image_cache_discover(Manager *m, Hashmap *images, sd_bus_error *error) {
Image *image;
Iterator i;
int r;
assert(m);
/* A wrapper around image_discover() (for finding images in search path) and portable_discover_attached() (for
* finding attached images). */
r = image_discover(IMAGE_PORTABLE, images);
if (r < 0)
return r;
HASHMAP_FOREACH(image, images, i)
(void) manager_image_cache_add(m, image);
return 0;
}

View File

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "hashmap.h"
#include "machine-image.h"
#include "portabled.h"
Image *manager_image_cache_get(Manager *m, const char *name_or_path);
int manager_image_cache_add(Manager *m, Image *image);
int manager_image_cache_discover(Manager *m, Hashmap *images, sd_bus_error *error);

View File

@ -0,0 +1,128 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "fd-util.h"
#include "portabled-operation.h"
#include "process-util.h"
static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
Operation *o = userdata;
int r;
assert(o);
assert(si);
log_debug("Operating " PID_FMT " is now complete with code=%s status=%i",
o->pid,
sigchld_code_to_string(si->si_code), si->si_status);
o->pid = 0;
if (si->si_code != CLD_EXITED) {
r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
goto fail;
}
if (si->si_status == EXIT_SUCCESS)
r = 0;
else if (read(o->errno_fd, &r, sizeof(r)) != sizeof(r)) { /* Try to acquire error code for failed operation */
r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed.");
goto fail;
}
if (o->done) {
/* A completion routine is set for this operation, call it. */
r = o->done(o, r, &error);
if (r < 0) {
if (!sd_bus_error_is_set(&error))
sd_bus_error_set_errno(&error, r);
goto fail;
}
} else {
/* The default operation when done is to simply return an error on failure or an empty success
* message on success. */
if (r < 0) {
sd_bus_error_set_errno(&error, r);
goto fail;
}
r = sd_bus_reply_method_return(o->message, NULL);
if (r < 0)
log_error_errno(r, "Failed to reply to message: %m");
}
operation_free(o);
return 0;
fail:
r = sd_bus_reply_method_error(o->message, &error);
if (r < 0)
log_error_errno(r, "Failed to reply to message: %m");
operation_free(o);
return 0;
}
int operation_new(Manager *manager, pid_t child, sd_bus_message *message, int errno_fd, Operation **ret) {
Operation *o;
int r;
assert(manager);
assert(child > 1);
assert(message);
assert(errno_fd >= 0);
o = new0(Operation, 1);
if (!o)
return -ENOMEM;
o->extra_fd = -1;
r = sd_event_add_child(manager->event, &o->event_source, child, WEXITED, operation_done, o);
if (r < 0) {
free(o);
return r;
}
o->pid = child;
o->message = sd_bus_message_ref(message);
o->errno_fd = errno_fd;
LIST_PREPEND(operations, manager->operations, o);
manager->n_operations++;
o->manager = manager;
log_debug("Started new operation " PID_FMT ".", child);
/* At this point we took ownership of both the child and the errno file descriptor! */
if (ret)
*ret = o;
return 0;
}
Operation *operation_free(Operation *o) {
if (!o)
return NULL;
sd_event_source_unref(o->event_source);
safe_close(o->errno_fd);
safe_close(o->extra_fd);
if (o->pid > 1)
(void) sigkill_wait(o->pid);
sd_bus_message_unref(o->message);
if (o->manager) {
LIST_REMOVE(operations, o->manager->operations, o);
o->manager->n_operations--;
}
return mfree(o);
}

View File

@ -0,0 +1,29 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include <sys/types.h>
#include "sd-bus.h"
#include "sd-event.h"
#include "list.h"
typedef struct Operation Operation;
#include "portabled.h"
#define OPERATIONS_MAX 64
struct Operation {
Manager *manager;
pid_t pid;
sd_bus_message *message;
int errno_fd;
int extra_fd;
sd_event_source *event_source;
int (*done)(Operation *o, int ret, sd_bus_error *error);
LIST_FIELDS(Operation, operations);
};
int operation_new(Manager *manager, pid_t child, sd_bus_message *message, int errno_fd, Operation **ret);
Operation *operation_free(Operation *o);

168
src/portable/portabled.c Normal file
View File

@ -0,0 +1,168 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "sd-bus.h"
#include "sd-daemon.h"
#include "alloc-util.h"
#include "bus-util.h"
#include "def.h"
#include "portabled-bus.h"
#include "portabled-image-bus.h"
#include "portabled.h"
#include "process-util.h"
#include "signal-util.h"
static Manager* manager_unref(Manager *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
static int manager_new(Manager **ret) {
_cleanup_(manager_unrefp) Manager *m = NULL;
int r;
assert(ret);
m = new0(Manager, 1);
if (!m)
return -ENOMEM;
r = sd_event_default(&m->event);
if (r < 0)
return r;
r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
if (r < 0)
return r;
r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
if (r < 0)
return r;
(void) sd_event_set_watchdog(m->event, true);
*ret = TAKE_PTR(m);
return 0;
}
static Manager* manager_unref(Manager *m) {
assert(m);
hashmap_free_with_destructor(m->image_cache, image_unref);
sd_event_source_unref(m->image_cache_defer_event);
bus_verify_polkit_async_registry_free(m->polkit_registry);
sd_bus_unref(m->bus);
sd_event_unref(m->event);
return mfree(m);
}
static int manager_connect_bus(Manager *m) {
int r;
assert(m);
assert(!m->bus);
r = sd_bus_default_system(&m->bus);
if (r < 0)
return log_error_errno(r, "Failed to connect to system bus: %m");
r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/portable1", "org.freedesktop.portable1.Manager", manager_vtable, m);
if (r < 0)
return log_error_errno(r, "Failed to add manager object vtable: %m");
r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/portable1/image", "org.freedesktop.portable1.Image", image_vtable, bus_image_object_find, m);
if (r < 0)
return log_error_errno(r, "Failed to add image object vtable: %m");
r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/portable1/image", bus_image_node_enumerator, m);
if (r < 0)
return log_error_errno(r, "Failed to add image enumerator: %m");
r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.portable1", 0, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to request name: %m");
r = sd_bus_attach_event(m->bus, m->event, 0);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
(void) sd_bus_set_exit_on_disconnect(m->bus, true);
return 0;
}
static int manager_startup(Manager *m) {
int r;
assert(m);
r = manager_connect_bus(m);
if (r < 0)
return r;
return 0;
}
static bool check_idle(void *userdata) {
Manager *m = userdata;
return !m->operations;
}
static int manager_run(Manager *m) {
assert(m);
return bus_event_loop_with_idle(
m->event,
m->bus,
"org.freedesktop.portable1",
DEFAULT_EXIT_USEC,
check_idle, m);
}
int main(int argc, char *argv[]) {
_cleanup_(manager_unrefp) Manager *m = NULL;
int r;
log_set_target(LOG_TARGET_AUTO);
log_parse_environment();
log_open();
umask(0022);
if (argc != 1) {
log_error("This program takes no arguments.");
r = -EINVAL;
goto finish;
}
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, -1) >= 0);
r = manager_new(&m);
if (r < 0) {
log_error_errno(r, "Failed to allocate manager object: %m");
goto finish;
}
r = manager_startup(m);
if (r < 0) {
log_error_errno(r, "Failed to fully start up daemon: %m");
goto finish;
}
log_debug("systemd-portabled running as pid " PID_FMT, getpid_cached());
sd_notify(false,
"READY=1\n"
"STATUS=Processing requests...");
r = manager_run(m);
log_debug("systemd-portabled stopped as pid " PID_FMT, getpid_cached());
finish:
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

25
src/portable/portabled.h Normal file
View File

@ -0,0 +1,25 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "sd-bus.h"
#include "sd-event.h"
#include "hashmap.h"
#include "list.h"
typedef struct Manager Manager;
#include "portabled-operation.h"
struct Manager {
sd_event *event;
sd_bus *bus;
Hashmap *polkit_registry;
Hashmap *image_cache;
sd_event_source *image_cache_defer_event;
LIST_HEAD(Operation, operations);
unsigned n_operations;
};

View File

@ -0,0 +1,30 @@
# The "default" security profile for services, i.e. a number of useful restrictions
[Service]
MountAPIVFS=yes
TemporaryFileSystem=/run
BindReadOnlyPaths=/run/systemd/notify
BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout
BindReadOnlyPaths=/etc/machine-id
BindReadOnlyPaths=/etc/resolv.conf
BindReadOnlyPaths=/run/dbus/system_bus_socket
DynamicUser=yes
RemoveIPC=yes
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER \
CAP_FSETID CAP_IPC_LOCK CAP_IPC_OWNER CAP_KILL CAP_MKNOD CAP_NET_ADMIN \
CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_SETGID CAP_SETPCAP \
CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT CAP_SYS_NICE CAP_SYS_RESOURCE
PrivateTmp=yes
PrivateDevices=yes
PrivateUsers=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
LockPersonality=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictNamespaces=yes
SystemCallArchitectures=native

View File

@ -0,0 +1,30 @@
# The "nonetwork" security profile for services, i.e. like "default" but without networking
[Service]
MountAPIVFS=yes
TemporaryFileSystem=/run
BindReadOnlyPaths=/run/systemd/notify
BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout
BindReadOnlyPaths=/etc/machine-id
BindReadOnlyPaths=/run/dbus/system_bus_socket
DynamicUser=yes
RemoveIPC=yes
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER \
CAP_FSETID CAP_IPC_LOCK CAP_IPC_OWNER CAP_KILL CAP_MKNOD CAP_SETGID CAP_SETPCAP \
CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT CAP_SYS_NICE CAP_SYS_RESOURCE
PrivateTmp=yes
PrivateDevices=yes
PrivateUsers=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK
LockPersonality=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictNamespaces=yes
SystemCallArchitectures=native
PrivateNetwork=yes
IPAddressDeny=any

View File

@ -0,0 +1,29 @@
# The "strict" security profile for services, all options turned on
[Service]
MountAPIVFS=yes
TemporaryFileSystem=/run
BindReadOnlyPaths=/run/systemd/notify
BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout
BindReadOnlyPaths=/etc/machine-id
DynamicUser=yes
RemoveIPC=yes
CapabilityBoundingSet=
PrivateTmp=yes
PrivateDevices=yes
PrivateUsers=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX
LockPersonality=yes
NoNewPrivileges=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictNamespaces=yes
SystemCallArchitectures=native
PrivateNetwork=yes
IPAddressDeny=any
TasksMax=4

View File

@ -0,0 +1,7 @@
# The "trusted" profile for services, i.e. no restrictions are applied
[Service]
MountAPIVFS=yes
BindPaths=/run
BindReadOnlyPaths=/etc/machine-id
BindReadOnlyPaths=/etc/resolv.conf

View File

@ -8,6 +8,7 @@ tmpfiles = [['home.conf', ''],
['journal-nocow.conf', ''],
['systemd-nologin.conf', ''],
['systemd-nspawn.conf', 'ENABLE_MACHINED'],
['portables.conf', 'ENABLE_PORTABLED'],
['tmp.conf', ''],
['x11.conf', ''],
['legacy.conf', 'HAVE_SYSV_COMPAT'],

View File

@ -0,0 +1,4 @@
# SPDX-License-Identifier: LGPL-2.1+
# See tmpfiles.d(5) for details
Q /var/lib/portables 0700

View File

@ -177,6 +177,8 @@ in_units = [
['systemd-networkd-wait-online.service', 'ENABLE_NETWORKD',
join_paths(pkgsysconfdir, 'system/network-online.target.wants/')],
['systemd-nspawn@.service', ''],
['systemd-portabled.service', 'ENABLE_PORTABLED',
'dbus-org.freedesktop.portable1.service'],
['systemd-poweroff.service', ''],
['systemd-quotacheck.service', 'ENABLE_QUOTACHECK'],
['systemd-random-seed.service', 'ENABLE_RANDOMSEED',

View File

@ -0,0 +1,26 @@
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# 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.
[Unit]
Description=Portable Service Manager
Documentation=man:systemd-portabled.service(8)
RequiresMountsFor=/var/lib/portables
[Service]
ExecStart=@rootlibexecdir@/systemd-portabled
BusName=org.freedesktop.portable1
WatchdogSec=3min
CapabilityBoundingSet=CAP_KILL CAP_SYS_PTRACE CAP_SYS_ADMIN CAP_SETGID CAP_SYS_CHROOT CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_CHOWN CAP_FOWNER CAP_FSETID CAP_MKNOD
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
SystemCallFilter=~@clock @cpu-emulation @debug @keyring @module @obsolete @raw-io @reboot @swap
SystemCallArchitectures=native
LockPersonality=yes
IPAddressDeny=any