mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
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:
parent
19017acb9f
commit
61d0578b07
24
meson.build
24
meson.build
@ -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'],
|
||||
|
@ -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',
|
||||
|
@ -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
29
src/portable/meson.build
Normal 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
|
117
src/portable/org.freedesktop.portable1.conf
Normal file
117
src/portable/org.freedesktop.portable1.conf
Normal 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>
|
43
src/portable/org.freedesktop.portable1.policy
Normal file
43
src/portable/org.freedesktop.portable1.policy
Normal 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>
|
7
src/portable/org.freedesktop.portable1.service
Normal file
7
src/portable/org.freedesktop.portable1.service
Normal 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
1427
src/portable/portable.c
Normal file
File diff suppressed because it is too large
Load Diff
77
src/portable/portable.h
Normal file
77
src/portable/portable.h
Normal 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
965
src/portable/portablectl.c
Normal 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;
|
||||
}
|
402
src/portable/portabled-bus.c
Normal file
402
src/portable/portabled-bus.c
Normal 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);
|
||||
}
|
10
src/portable/portabled-bus.h
Normal file
10
src/portable/portabled-bus.h
Normal 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);
|
733
src/portable/portabled-image-bus.c
Normal file
733
src/portable/portabled-image-bus.c
Normal 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, ©_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;
|
||||
}
|
40
src/portable/portabled-image-bus.h
Normal file
40
src/portable/portabled-image-bus.h
Normal 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);
|
105
src/portable/portabled-image.c
Normal file
105
src/portable/portabled-image.c
Normal 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;
|
||||
}
|
11
src/portable/portabled-image.h
Normal file
11
src/portable/portabled-image.h
Normal 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);
|
128
src/portable/portabled-operation.c
Normal file
128
src/portable/portabled-operation.c
Normal 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);
|
||||
}
|
29
src/portable/portabled-operation.h
Normal file
29
src/portable/portabled-operation.h
Normal 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
168
src/portable/portabled.c
Normal 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
25
src/portable/portabled.h
Normal 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;
|
||||
};
|
30
src/portable/profile/default/service.conf
Normal file
30
src/portable/profile/default/service.conf
Normal 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
|
30
src/portable/profile/nonetwork/service.conf
Normal file
30
src/portable/profile/nonetwork/service.conf
Normal 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
|
29
src/portable/profile/strict/service.conf
Normal file
29
src/portable/profile/strict/service.conf
Normal 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
|
7
src/portable/profile/trusted/service.conf
Normal file
7
src/portable/profile/trusted/service.conf
Normal 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
|
@ -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'],
|
||||
|
4
tmpfiles.d/portables.conf
Normal file
4
tmpfiles.d/portables.conf
Normal file
@ -0,0 +1,4 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1+
|
||||
# See tmpfiles.d(5) for details
|
||||
|
||||
Q /var/lib/portables 0700
|
@ -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',
|
||||
|
26
units/systemd-portabled.service.in
Normal file
26
units/systemd-portabled.service.in
Normal 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
|
Loading…
Reference in New Issue
Block a user