Merge pull request #27041 from poettering/fdstore-dump

add ability to show contents of service fdstore + teach systemd-notify passing fds into the fdstore
This commit is contained in:
Daan De Meyer
2023-03-30 09:32:13 +02:00
committed by GitHub
30 changed files with 629 additions and 81 deletions
-4
View File
@@ -136,10 +136,6 @@ Features:
always end in a colon. This requires updating our udev rules, as well as
checking if the various hwdb files are fine with that.
* Add a bus API to enumerate contents of the fdstore of a service,
handle/display similar to querying the process tree. Should probably just an
array of inode/devnum of fd, plus fd_get_name() data
* mount /tmp/ and /var/tmp with a uidmap applied that blocks out "nobody" user
among other things such as dynamic uid ranges for containers and so on. That
way noone can create files there with these uids and we enforce they are only
+22
View File
@@ -273,6 +273,8 @@ node /org/freedesktop/systemd1 {
LookupDynamicUserByUID(in u uid,
out s name);
GetDynamicUsers(out a(us) users);
DumpUnitFileDescriptorStore(in s name,
out a(suuutuusu) entries);
signals:
UnitNew(s id,
o unit);
@@ -974,6 +976,8 @@ node /org/freedesktop/systemd1 {
<variablelist class="dbus-method" generated="True" extra-ref="GetDynamicUsers()"/>
<variablelist class="dbus-method" generated="True" extra-ref="DumpUnitFileDescriptorStore()"/>
<variablelist class="dbus-signal" generated="True" extra-ref="UnitNew"/>
<variablelist class="dbus-signal" generated="True" extra-ref="UnitRemoved"/>
@@ -1531,6 +1535,11 @@ node /org/freedesktop/systemd1 {
<ulink url="https://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface">New Control Group
Interface</ulink> for more information how to make use of this functionality for resource control
purposes.</para>
<para><function>DumpUnitFileDescriptorStore()</function> returns an array with information about the
file descriptors currently in the file descriptor store of the specified unit. This call is equivalent
to <function>DumpFileDescriptorStore()</function> on the
<interfacename>org.freedesktop.systemd1.Service</interfacename>. For further details, see below.</para>
</refsect2>
<refsect2>
@@ -2548,6 +2557,7 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
in b read_only,
in b mkdir,
in a(ss) options);
DumpFileDescriptorStore(out a(suuutuusu) entries);
GetProcesses(out a(sus) processes);
AttachProcesses(in s subcgroup,
in au pids);
@@ -3742,6 +3752,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-method" generated="True" extra-ref="MountImage()"/>
<variablelist class="dbus-method" generated="True" extra-ref="DumpFileDescriptorStore()"/>
<variablelist class="dbus-method" generated="True" extra-ref="GetProcesses()"/>
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
@@ -4393,6 +4405,16 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
directly on the Manager object has the advantage of not requiring a <function>GetUnit()</function> call
to get the unit object for a specific unit name. Calling the methods on the Manager object is hence a round
trip optimization.</para>
<para><function>DumpFileDescriptorStore()</function> returns an array with information about the file
descriptors currently in the file descriptor store of the service. Each entry consists of a file
descriptor name (i.e. the <varname>FDNAME=</varname> field), the file descriptor inode type and access
mode as integer (i.e. a <type>mode_t</type> value, flags such as <constant>S_IFREG</constant>,
<constant>S_IRUSR</constant>, …), the major and minor numbers of the device number of the file system
backing the inode of the file descriptor, the inode number, the major and minor numbers of the device
number if this refers to a character or block device node, a file system path pointing to the inode,
and the file descriptor flags (i.e. <constant>O_RDWR</constant>, <constant>O_RDONLY</constant>,
…).</para>
</refsect2>
<refsect2>
+36 -1
View File
@@ -156,6 +156,12 @@
<arg choice="plain">malloc</arg>
<arg choice="opt" rep="repeat"><replaceable>D-BUS SERVICE</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">fdstore</arg>
<arg choice="opt" rep="repeat"><replaceable>UNIT</replaceable></arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
@@ -803,8 +809,37 @@ $ systemd-analyze verify /tmp/source:alias.service
}
</programlisting>
</example>
</refsect2>
<refsect2>
<title><command>systemd-analyze fdstore <optional><replaceable>UNIT</replaceable>...</optional></command></title>
<para>Lists the current contents of the specified service unit's file descriptor store. This shows
names, inode types, device numbers, inode numbers, paths and open modes of the open file
descriptors. The specified units must have <varname>FileDescriptorStoreMax=</varname> enabled, see
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details.</para>
<example>
<title>Table output</title>
<programlisting>$ systemd-analyze fdstore systemd-journald.service
FDNAME TYPE DEVNO INODE RDEVNO PATH FLAGS
stored sock 0:8 4218620 - socket:[4218620] ro
stored sock 0:8 4213198 - socket:[4213198] ro
stored sock 0:8 4213190 - socket:[4213190] ro
…</programlisting>
</example>
<para>Note: the "DEVNO" column refers to the major/minor numbers of the device node backing the file
system the file descriptor's inode is on. The "RDEVNO" column refers to the major/minor numbers of the
device node itself if the file descriptor refers to one. Compare with corresponding
<varname>.st_dev</varname> and <varname>.st_rdev</varname> fields in <type>struct stat</type> (see
<citerefentry
project='man-pages'><refentrytitle>stat</refentrytitle><manvolnum>2</manvolnum></citerefentry> for
details). The listed inode numbers in the "INODE" column are on the file system indicated by
"DEVNO".</para>
</refsect2>
</refsect1>
<refsect1>
+23
View File
@@ -191,6 +191,29 @@
escaped as <literal>\;</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--fd=</option></term>
<listitem><para>Send a file descriptor along with the notification message. This is useful when
invoked in services that have the <varname>FileDescriptorStoreMax=</varname> setting enabled, see
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details. The specified file descriptor must be passed to <command>systemd-notify</command> when
invoked. This option may be used multiple times to pass multiple file descriptors in a single
notification message.</para>
<para>To use this functionality from a <command>bash</command> shell, use an expression like the following:</para>
<programlisting>systemd-notify --fd=4 --fd=5 4&lt;/some/file 5&lt;/some/other/file</programlisting></listitem>
</varlistentry>
<varlistentry>
<term><option>--fdname=</option></term>
<listitem><para>Set a name to assign to the file descriptors passed via <option>--fd=</option> (see
above). This controls the <literal>FDNAME=</literal> field. This setting may only be specified once,
and applies to all file descriptors passed. Invoke this tool multiple times in case multiple file
descriptors with different file descriptor names shall be submitted.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
+12 -1
View File
@@ -1141,7 +1141,18 @@
fully stopped and no job is queued or being executed for it. If this option is used,
<varname>NotifyAccess=</varname> (see above) should be set to open access to the notification socket
provided by systemd. If <varname>NotifyAccess=</varname> is not set, it will be implicitly set to
<option>main</option>.</para></listitem>
<option>main</option>.</para>
<para>The <command>fdstore</command> command of
<citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>
may be used to list the current contents of a service's file descriptor store.</para>
<para>Note that the service manager will only pass file descriptors contained in the file descriptor
store to the service's own processes, never to other clients via IPC or similar. However, it does
allow unprivileged clients to query the list of currently open file descriptors of a
service. Sensitive data may hence be safely placed inside the referenced files, but should not be
attached to the metadata (e.g. included in filenames) of the stored file
descriptors.</para></listitem>
</varlistentry>
<varlistentry>
+110
View File
@@ -0,0 +1,110 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze-fdstore.h"
#include "analyze.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "fd-util.h"
#include "format-table.h"
static int dump_fdstore(sd_bus *bus, const char *arg) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *unit = NULL;
int r;
assert(bus);
assert(arg);
r = unit_name_mangle_with_suffix(arg, NULL, UNIT_NAME_MANGLE_GLOB, ".service", &unit);
if (r < 0)
return log_error_errno(r, "Failed to mangle name '%s': %m", arg);
r = bus_call_method(
bus,
bus_systemd_mgr,
"DumpUnitFileDescriptorStore",
&error,
&reply,
"s", unit);
if (r < 0)
return log_error_errno(r, "Failed to call DumpUnitFileDescriptorStore: %s",
bus_error_message(&error, r));
r = sd_bus_message_enter_container(reply, 'a', "(suuutuusu)");
if (r < 0)
return bus_log_parse_error(r);
table = table_new("fdname", "type", "devno", "inode", "rdevno", "path", "flags");
if (!table)
return log_oom();
table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
(void) table_set_align_percent(table, TABLE_HEADER_CELL(3), 100);
for (;;) {
uint32_t mode, major, minor, rmajor, rminor, flags;
const char *fdname, *path;
uint64_t inode;
r = sd_bus_message_read(
reply,
"(suuutuusu)",
&fdname,
&mode,
&major, &minor,
&inode,
&rmajor, &rminor,
&path,
&flags);
if (r < 0)
return bus_log_parse_error(r);
if (r == 0)
break;
r = table_add_many(
table,
TABLE_STRING, fdname,
TABLE_MODE_INODE_TYPE, mode,
TABLE_DEVNUM, makedev(major, minor),
TABLE_UINT64, inode,
TABLE_DEVNUM, makedev(rmajor, rminor),
TABLE_PATH, path,
TABLE_STRING, accmode_to_string(flags));
if (r < 0)
return table_log_add_error(r);
}
r = sd_bus_message_exit_container(reply);
if (r < 0)
return r;
if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && table_get_rows(table) <= 0)
log_info("No file descriptors in fdstore of '%s'.", unit);
else {
r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */true);
if (r < 0)
return log_error_errno(r, "Failed to output table: %m");
}
return EXIT_SUCCESS;
}
int verb_fdstore(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
r = acquire_bus(&bus, NULL);
if (r < 0)
return bus_log_connect_error(r, arg_transport);
STRV_FOREACH(arg, strv_skip(argv, 1)) {
r = dump_fdstore(bus, *arg);
if (r < 0)
return r;
}
return EXIT_SUCCESS;
}
+5
View File
@@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-bus.h"
int verb_fdstore(int argc, char *argv[], void *userdata);
+6 -3
View File
@@ -17,11 +17,13 @@
#include "analyze-calendar.h"
#include "analyze-capability.h"
#include "analyze-cat-config.h"
#include "analyze-compare-versions.h"
#include "analyze-condition.h"
#include "analyze-critical-chain.h"
#include "analyze-dot.h"
#include "analyze-dump.h"
#include "analyze-exit-status.h"
#include "analyze-fdstore.h"
#include "analyze-filesystems.h"
#include "analyze-inspect-elf.h"
#include "analyze-log-control.h"
@@ -36,7 +38,6 @@
#include "analyze-timestamp.h"
#include "analyze-unit-files.h"
#include "analyze-unit-paths.h"
#include "analyze-compare-versions.h"
#include "analyze-verify.h"
#include "build.h"
#include "bus-error.h"
@@ -229,6 +230,7 @@ static int help(int argc, char *argv[], void *userdata) {
" security [UNIT...] Analyze security of unit\n"
" inspect-elf FILE... Parse and print ELF package metadata\n"
" malloc [D-BUS SERVICE...] Dump malloc stats of a D-Bus service\n"
" fdstore SERVICE... Show file descriptor store contents of service\n"
"\nOptions:\n"
" --recursive-errors=MODE Control which units are verified\n"
" --offline=BOOL Perform a security review on unit file(s)\n"
@@ -531,9 +533,9 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --offline= is only supported for security right now.");
if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot"))
if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot", "fdstore"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --json= is only supported for security, inspect-elf, and plot right now.");
"Option --json= is only supported for security, inspect-elf, plot, and fdstore right now.");
if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -620,6 +622,7 @@ static int run(int argc, char *argv[]) {
{ "security", VERB_ANY, VERB_ANY, 0, verb_security },
{ "inspect-elf", 2, VERB_ANY, 0, verb_elf_inspection },
{ "malloc", VERB_ANY, VERB_ANY, 0, verb_malloc },
{ "fdstore", 2, VERB_ANY, 0, verb_fdstore },
{}
};
+1
View File
@@ -11,6 +11,7 @@ systemd_analyze_sources = files(
'analyze-dot.c',
'analyze-dump.c',
'analyze-exit-status.c',
'analyze-fdstore.c',
'analyze-filesystems.c',
'analyze-inspect-elf.c',
'analyze-log-control.c',
+1 -1
View File
@@ -83,7 +83,7 @@ int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret) {
assert(ret);
if (major(devnum) == 0 && minor(devnum) == 0)
if (devnum_is_zero(devnum))
/* A special hack to make sure our 'inaccessible' device nodes work. They won't have symlinks in
* /dev/block/ and /dev/char/, hence we handle them specially here. */
return device_path_make_inaccessible(mode, ret);
+4
View File
@@ -50,3 +50,7 @@ static inline char *format_devnum(dev_t d, char buf[static DEVNUM_STR_MAX]) {
}
#define FORMAT_DEVNUM(d) format_devnum((d), (char[DEVNUM_STR_MAX]) {})
static inline bool devnum_is_zero(dev_t d) {
return major(d) == 0 && minor(d) == 0;
}
+13
View File
@@ -940,3 +940,16 @@ int dir_fd_is_root(int dir_fd) {
*/
return statx_inode_same(&st.sx, &pst.sx) && statx_mount_same(&st.nsx, &pst.nsx);
}
const char *accmode_to_string(int flags) {
switch (flags & O_ACCMODE) {
case O_RDONLY:
return "ro";
case O_WRONLY:
return "wo";
case O_RDWR:
return "rw";
default:
return NULL;
}
}
+2
View File
@@ -115,3 +115,5 @@ static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int f
#define FORMAT_PROC_FD_PATH(fd) \
format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd))
const char *accmode_to_string(int flags);
+9
View File
@@ -58,3 +58,12 @@
#ifndef O_TMPFILE
#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
#endif
/* So O_LARGEFILE is generally implied by glibc, and defined to zero hence, because we only build in LFS
* mode. However, when invoking fcntl(F_GETFL) the flag is ORed into the result anyway — glibc does not mask
* it away. Which sucks. Let's define the actual value here, so that we can mask it ourselves. */
#if O_LARGEFILE != 0
#define RAW_O_LARGEFILE O_LARGEFILE
#else
#define RAW_O_LARGEFILE 0100000
#endif
+23
View File
@@ -471,3 +471,26 @@ int inode_compare_func(const struct stat *a, const struct stat *b) {
}
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
const char* inode_type_to_string(mode_t m) {
/* Returns a short string for the inode type. We use the same name as the underlying macros for each
* inode type. */
switch (m & S_IFMT) {
case S_IFREG:
return "reg";
case S_IFDIR:
return "dir";
case S_IFCHR:
return "chr";
case S_IFBLK:
return "blk";
case S_IFIFO:
return "fifo";
case S_IFSOCK:
return "sock";
}
return NULL;
}
+2
View File
@@ -101,3 +101,5 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s
void inode_hash_func(const struct stat *q, struct siphash *state);
int inode_compare_func(const struct stat *a, const struct stat *b);
extern const struct hash_ops inode_hash_ops;
const char* inode_type_to_string(mode_t m);
+1 -1
View File
@@ -979,7 +979,7 @@ int get_ctty_devnr(pid_t pid, dev_t *d) {
&ttynr) != 1)
return -EIO;
if (major(ttynr) == 0 && minor(ttynr) == 0)
if (devnum_is_zero(ttynr))
return -ENXIO;
if (d)
+9
View File
@@ -2840,6 +2840,10 @@ static int method_set_show_status(sd_bus_message *message, void *userdata, sd_bu
return sd_bus_reply_method_return(message, NULL);
}
static int method_dump_unit_descriptor_store(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return method_generic_unit_operation(message, userdata, error, bus_service_method_dump_file_descriptor_store, 0);
}
const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_VTABLE_START(0),
@@ -3385,6 +3389,11 @@ const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_RESULT("a(us)", users),
method_get_dynamic_users,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("DumpUnitFileDescriptorStore",
SD_BUS_ARGS("s", name),
SD_BUS_RESULT("a(suuutuusu)", entries),
method_dump_unit_descriptor_store,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL_WITH_ARGS("UnitNew",
SD_BUS_ARGS("s", id, "o", unit),
+74
View File
@@ -4,6 +4,7 @@
#include "alloc-util.h"
#include "async.h"
#include "bus-common-errors.h"
#include "bus-get-properties.h"
#include "dbus-cgroup.h"
#include "dbus-execute.h"
@@ -16,6 +17,7 @@
#include "fd-util.h"
#include "fileio.h"
#include "locale-util.h"
#include "missing_fcntl.h"
#include "mount-util.h"
#include "open-file.h"
#include "parse-util.h"
@@ -218,6 +220,72 @@ int bus_service_method_mount_image(sd_bus_message *message, void *userdata, sd_b
return bus_service_method_mount(message, userdata, error, true);
}
int bus_service_method_dump_file_descriptor_store(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
Service *s = ASSERT_PTR(userdata);
int r;
assert(message);
r = mac_selinux_unit_access_check(UNIT(s), message, "status", error);
if (r < 0)
return r;
if (s->n_fd_store_max == 0 && s->n_fd_store == 0)
return sd_bus_error_setf(error, BUS_ERROR_FILE_DESCRIPTOR_STORE_DISABLED, "File descriptor store not enabled for %s.", UNIT(s)->id);
r = sd_bus_message_new_method_return(message, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "(suuutuusu)");
if (r < 0)
return r;
LIST_FOREACH(fd_store, i, s->fd_store) {
_cleanup_free_ char *path = NULL;
struct stat st;
int flags;
if (fstat(i->fd, &st) < 0) {
log_debug_errno(errno, "Failed to stat() file descriptor entry '%s', skipping.", strna(i->fdname));
continue;
}
flags = fcntl(i->fd, F_GETFL);
if (flags < 0) {
log_debug_errno(errno, "Failed to issue F_GETFL on file descriptor entry '%s', skipping.", strna(i->fdname));
continue;
}
/* glibc implies O_LARGEFILE everywhere on 64bit off_t builds, but forgets to hide it away on
* F_GETFL, but provides no definition to check for that. Let's mask the flag away manually,
* to not confuse clients. */
flags &= ~RAW_O_LARGEFILE;
(void) fd_get_path(i->fd, &path);
r = sd_bus_message_append(
reply,
"(suuutuusu)",
i->fdname,
(uint32_t) st.st_mode,
(uint32_t) major(st.st_dev), (uint32_t) minor(st.st_dev),
(uint64_t) st.st_ino,
(uint32_t) major(st.st_rdev), (uint32_t) minor(st.st_rdev),
path,
(uint32_t) flags);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}
const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -292,6 +360,12 @@ const sd_bus_vtable bus_service_vtable[] = {
bus_service_method_mount_image,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("DumpFileDescriptorStore",
SD_BUS_NO_ARGS,
SD_BUS_ARGS("a(suuutuusu)", entries),
bus_service_method_dump_file_descriptor_store,
SD_BUS_VTABLE_UNPRIVILEGED),
/* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */
SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_ratelimit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_ratelimit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
+1
View File
@@ -12,3 +12,4 @@ int bus_service_set_property(Unit *u, const char *name, sd_bus_message *i, UnitW
int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_service_method_mount_image(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_service_commit_properties(Unit *u);
int bus_service_method_dump_file_descriptor_store(sd_bus_message *message, void *userdata, sd_bus_error *error);
@@ -32,6 +32,8 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DYNAMIC_USER, ESRCH),
SD_BUS_ERROR_MAP(BUS_ERROR_NOT_REFERENCED, EUNATCH),
SD_BUS_ERROR_MAP(BUS_ERROR_DISK_FULL, ENOSPC),
SD_BUS_ERROR_MAP(BUS_ERROR_FILE_DESCRIPTOR_STORE_DISABLED,
EHOSTDOWN),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_MACHINE, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT),
@@ -32,6 +32,8 @@
#define BUS_ERROR_UNIT_BUSY "org.freedesktop.systemd1.UnitBusy"
#define BUS_ERROR_UNIT_INACTIVE "org.freedesktop.systemd1.UnitInactive"
#define BUS_ERROR_FREEZE_CANCELLED "org.freedesktop.systemd1.FreezeCancelled"
#define BUS_ERROR_FILE_DESCRIPTOR_STORE_DISABLED \
"org.freedesktop.systemd1.FileDescriptorStoreDisabled"
#define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine"
#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage"
+1 -1
View File
@@ -15,7 +15,7 @@ int devname_from_devnum(mode_t mode, dev_t devnum, char **ret) {
assert(ret);
if (major(devnum) == 0 && minor(devnum) == 0)
if (devnum_is_zero(devnum))
return device_path_make_inaccessible(mode, ret);
r = device_new_from_mode_and_devnum(&dev, mode, devnum);
+105 -4
View File
@@ -11,6 +11,8 @@
#include "alloc-util.h"
#include "build.h"
#include "env-util.h"
#include "fd-util.h"
#include "fdset.h"
#include "format-util.h"
#include "log.h"
#include "main-func.h"
@@ -33,9 +35,13 @@ static gid_t arg_gid = GID_INVALID;
static bool arg_no_block = false;
static char **arg_env = NULL;
static char **arg_exec = NULL;
static FDSet *arg_fds = NULL;
static char *arg_fdname = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_exec, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_fds, fdset_freep);
STATIC_DESTRUCTOR_REGISTER(arg_fdname, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@@ -60,6 +66,8 @@ static int help(void) {
" --booted Check if the system was booted up with systemd\n"
" --no-block Do not wait until operation finished\n"
" --exec Execute command line separated by ';' once done\n"
" --fd=FD Pass specified file descriptor with along with message\n"
" --fdname=NAME Name to assign to passed file descriptor(s)\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
program_invocation_short_name,
@@ -103,6 +111,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_UID,
ARG_NO_BLOCK,
ARG_EXEC,
ARG_FD,
ARG_FDNAME,
};
static const struct option options[] = {
@@ -117,9 +127,12 @@ static int parse_argv(int argc, char *argv[]) {
{ "uid", required_argument, NULL, ARG_UID },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{ "exec", no_argument, NULL, ARG_EXEC },
{ "fd", required_argument, NULL, ARG_FD },
{ "fdname", required_argument, NULL, ARG_FDNAME },
{}
};
_cleanup_(fdset_freep) FDSet *passed = NULL;
bool do_exec = false;
int c, r, n_env;
@@ -198,6 +211,60 @@ static int parse_argv(int argc, char *argv[]) {
do_exec = true;
break;
case ARG_FD: {
_cleanup_close_ int owned_fd = -EBADF;
int fdnr;
r = safe_atoi(optarg, &fdnr);
if (r < 0)
return log_error_errno(r, "Failed to parse file descriptor: %s", optarg);
if (fdnr < 0)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "File descriptor can't be negative: %i", fdnr);
if (!passed) {
/* Take possession of all passed fds */
r = fdset_new_fill(&passed);
if (r < 0)
return log_error_errno(r, "Failed to take possession of passed file descriptors: %m");
r = fdset_cloexec(passed, true);
if (r < 0)
return log_error_errno(r, "Failed to enable O_CLOEXEC for passed file descriptors: %m");
}
if (fdnr < 3) {
/* For stdin/stdout/stderr we want to keep the fd, too, hence make a copy */
owned_fd = fcntl(fdnr, F_DUPFD_CLOEXEC, 3);
if (owned_fd < 0)
return log_error_errno(errno, "Failed to duplicate file descriptor: %m");
} else {
/* Otherwise, move the fd over */
owned_fd = fdset_remove(passed, fdnr);
if (owned_fd < 0)
return log_error_errno(owned_fd, "Specified file descriptor '%i' not passed or specified more than once: %m", fdnr);
}
if (!arg_fds) {
arg_fds = fdset_new();
if (!arg_fds)
return log_oom();
}
r = fdset_consume(arg_fds, TAKE_FD(owned_fd));
if (r < 0)
return log_error_errno(r, "Failed to add file descriptor to set: %m");
break;
}
case ARG_FDNAME:
if (!fdname_is_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", optarg);
if (free_and_strdup(&arg_fdname, optarg) < 0)
return log_oom();
break;
case '?':
return -EINVAL;
@@ -212,11 +279,15 @@ static int parse_argv(int argc, char *argv[]) {
!arg_reloading &&
!arg_status &&
!arg_pid &&
!arg_booted) {
!arg_booted &&
fdset_isempty(arg_fds)) {
help();
return -EINVAL;
}
if (arg_fdname && fdset_isempty(arg_fds))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed, but --fdname= set, refusing.");
if (do_exec) {
int i;
@@ -243,13 +314,16 @@ static int parse_argv(int argc, char *argv[]) {
return log_oom();
}
if (!fdset_isempty(passed))
log_warning("Warning: %u more file descriptors passed than referenced with --fd=.", fdset_size(passed));
return 1;
}
static int run(int argc, char* argv[]) {
_cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL, *monotonic_usec = NULL;
_cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL, *monotonic_usec = NULL, *fdn = NULL;
_cleanup_strv_free_ char **final_env = NULL;
char* our_env[7];
char* our_env[9];
size_t i = 0;
pid_t source_pid;
int r;
@@ -302,6 +376,18 @@ static int run(int argc, char* argv[]) {
our_env[i++] = cpid;
}
if (!fdset_isempty(arg_fds)) {
our_env[i++] = (char*) "FDSTORE=1";
if (arg_fdname) {
fdn = strjoin("FDNAME=", arg_fdname);
if (!fdn)
return log_oom();
our_env[i++] = fdn;
}
}
our_env[i++] = NULL;
final_env = strv_env_merge(our_env, arg_env);
@@ -338,13 +424,28 @@ static int run(int argc, char* argv[]) {
* or the service manager itself */
source_pid = 0;
}
r = sd_pid_notify(source_pid, false, n);
if (fdset_isempty(arg_fds))
r = sd_pid_notify(source_pid, /* unset_environment= */ false, n);
else {
_cleanup_free_ int *a = NULL;
int k;
k = fdset_to_array(arg_fds, &a);
if (k < 0)
return log_error_errno(k, "Failed to convert file descriptor set to array: %m");
r = sd_pid_notify_with_fds(source_pid, /* unset_environment= */ false, n, a, k);
}
if (r < 0)
return log_error_errno(r, "Failed to notify init system: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"No status data could be sent: $NOTIFY_SOCKET was not set");
arg_fds = fdset_free(arg_fds); /* Close before we execute anything */
if (!arg_no_block) {
r = sd_notify_barrier(0, 5 * USEC_PER_SEC);
if (r < 0)
+82 -61
View File
@@ -15,6 +15,7 @@
#include "parse-util.h"
#include "path-util.h"
#include "set.h"
#include "stat-util.h"
#define MAKE_SET(s) ((Set*) s)
#define MAKE_FDSET(s) ((FDSet*) s)
@@ -23,27 +24,29 @@ FDSet *fdset_new(void) {
return MAKE_FDSET(set_new(NULL));
}
int fdset_new_array(FDSet **ret, const int *fds, size_t n_fds) {
size_t i;
FDSet *s;
static inline void fdset_shallow_freep(FDSet **s) {
/* Destroys the set, but does not free the fds inside, like fdset_free()! */
set_free(MAKE_SET(*ASSERT_PTR(s)));
}
int fdset_new_array(FDSet **ret, const int fds[], size_t n_fds) {
_cleanup_(fdset_shallow_freep) FDSet *s = NULL;
int r;
assert(ret);
assert(fds || n_fds == 0);
s = fdset_new();
if (!s)
return -ENOMEM;
for (i = 0; i < n_fds; i++) {
for (size_t i = 0; i < n_fds; i++) {
r = fdset_put(s, fds[i]);
if (r < 0) {
set_free(MAKE_SET(s));
if (r < 0)
return r;
}
}
*ret = s;
*ret = TAKE_PTR(s);
return 0;
}
@@ -77,8 +80,22 @@ int fdset_put(FDSet *s, int fd) {
return set_put(MAKE_SET(s), FD_TO_PTR(fd));
}
int fdset_consume(FDSet *s, int fd) {
int r;
assert(s);
assert(fd >= 0);
r = fdset_put(s, fd);
if (r < 0)
safe_close(fd);
return r;
}
int fdset_put_dup(FDSet *s, int fd) {
int copy, r;
_cleanup_close_ int copy = -EBADF;
int r;
assert(s);
assert(fd >= 0);
@@ -88,12 +105,10 @@ int fdset_put_dup(FDSet *s, int fd) {
return -errno;
r = fdset_put(s, copy);
if (r < 0) {
safe_close(copy);
if (r < 0)
return r;
}
return copy;
return TAKE_FD(copy);
}
bool fdset_contains(FDSet *s, int fd) {
@@ -110,53 +125,46 @@ int fdset_remove(FDSet *s, int fd) {
return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT;
}
int fdset_new_fill(FDSet **_s) {
int fdset_new_fill(FDSet **ret) {
_cleanup_(fdset_shallow_freep) FDSet *s = NULL;
_cleanup_closedir_ DIR *d = NULL;
int r = 0;
FDSet *s;
int r;
assert(_s);
assert(ret);
/* Creates an fdset and fills in all currently open file
* descriptors. */
/* Creates an fdset and fills in all currently open file descriptors. */
d = opendir("/proc/self/fd");
if (!d)
if (!d) {
if (errno == ENOENT && proc_mounted() == 0)
return -ENOSYS;
return -errno;
}
s = fdset_new();
if (!s) {
r = -ENOMEM;
goto finish;
}
if (!s)
return -ENOMEM;
FOREACH_DIRENT(de, d, return -errno) {
int fd = -EBADF;
r = safe_atoi(de->d_name, &fd);
if (r < 0)
goto finish;
return r;
if (fd < 3)
continue;
if (fd == dirfd(d))
continue;
r = fdset_put(s, fd);
if (r < 0)
goto finish;
return r;
}
r = 0;
*_s = TAKE_PTR(s);
finish:
/* We won't close the fds here! */
if (s)
set_free(MAKE_SET(s));
return r;
*ret = TAKE_PTR(s);
return 0;
}
int fdset_cloexec(FDSet *fds, bool b) {
@@ -174,53 +182,66 @@ int fdset_cloexec(FDSet *fds, bool b) {
return 0;
}
int fdset_new_listen_fds(FDSet **_s, bool unset) {
int fdset_new_listen_fds(FDSet **ret, bool unset) {
_cleanup_(fdset_shallow_freep) FDSet *s = NULL;
int n, fd, r;
FDSet *s;
assert(_s);
assert(ret);
/* Creates an fdset and fills in all passed file descriptors */
s = fdset_new();
if (!s) {
r = -ENOMEM;
goto fail;
}
if (!s)
return -ENOMEM;
n = sd_listen_fds(unset);
for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) {
r = fdset_put(s, fd);
if (r < 0)
goto fail;
return r;
}
*_s = s;
*ret = TAKE_PTR(s);
return 0;
fail:
if (s)
set_free(MAKE_SET(s));
return r;
}
int fdset_close_others(FDSet *fds) {
int fdset_to_array(FDSet *fds, int **ret) {
unsigned j = 0, m;
void *e;
int *a = NULL;
size_t j = 0, m;
int *a;
assert(ret);
m = fdset_size(fds);
if (m > 0) {
a = newa(int, m);
SET_FOREACH(e, MAKE_SET(fds))
a[j++] = PTR_TO_FD(e);
if (m > INT_MAX) /* We want to be able to return an "int" */
return -ENOMEM;
if (m == 0) {
*ret = NULL; /* suppress array allocation if empty */
return 0;
}
a = new(int, m);
if (!a)
return -ENOMEM;
SET_FOREACH(e, MAKE_SET(fds))
a[j++] = PTR_TO_FD(e);
assert(j == m);
return close_all_fds(a, j);
*ret = TAKE_PTR(a);
return (int) m;
}
int fdset_close_others(FDSet *fds) {
_cleanup_free_ int *a = NULL;
int n;
n = fdset_to_array(fds, &a);
if (n < 0)
return n;
return close_all_fds(a, n);
}
unsigned fdset_size(FDSet *fds) {
+3
View File
@@ -13,6 +13,7 @@ FDSet* fdset_new(void);
FDSet* fdset_free(FDSet *s);
int fdset_put(FDSet *s, int fd);
int fdset_consume(FDSet *s, int fd);
int fdset_put_dup(FDSet *s, int fd);
bool fdset_contains(FDSet *s, int fd);
@@ -24,6 +25,8 @@ int fdset_new_listen_fds(FDSet **ret, bool unset);
int fdset_cloexec(FDSet *fds, bool b);
int fdset_to_array(FDSet *fds, int **ret);
int fdset_close_others(FDSet *fds);
unsigned fdset_size(FDSet *fds);
+49
View File
@@ -7,6 +7,7 @@
#include "sd-id128.h"
#include "alloc-util.h"
#include "devnum-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
@@ -24,6 +25,7 @@
#include "process-util.h"
#include "signal-util.h"
#include "sort-util.h"
#include "stat-util.h"
#include "string-util.h"
#include "strxcpyx.h"
#include "terminal-util.h"
@@ -107,6 +109,7 @@ typedef struct TableData {
gid_t gid;
pid_t pid;
mode_t mode;
dev_t devnum;
/* … add more here as we start supporting more cell data types … */
};
} TableData;
@@ -348,8 +351,12 @@ static size_t table_data_size(TableDataType type, const void *data) {
return sizeof(pid_t);
case TABLE_MODE:
case TABLE_MODE_INODE_TYPE:
return sizeof(mode_t);
case TABLE_DEVNUM:
return sizeof(dev_t);
default:
assert_not_reached();
}
@@ -867,6 +874,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
gid_t gid;
pid_t pid;
mode_t mode;
dev_t devnum;
} buffer;
switch (type) {
@@ -1022,10 +1030,16 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
break;
case TABLE_MODE:
case TABLE_MODE_INODE_TYPE:
buffer.mode = va_arg(ap, mode_t);
data = &buffer.mode;
break;
case TABLE_DEVNUM:
buffer.devnum = va_arg(ap, dev_t);
data = &buffer.devnum;
break;
case TABLE_SET_MINIMUM_WIDTH: {
size_t w = va_arg(ap, size_t);
@@ -1273,6 +1287,8 @@ int table_hide_column_from_display_internal(Table *t, ...) {
}
static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
int r;
assert(a);
assert(b);
@@ -1377,8 +1393,16 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
return CMP(a->pid, b->pid);
case TABLE_MODE:
case TABLE_MODE_INODE_TYPE:
return CMP(a->mode, b->mode);
case TABLE_DEVNUM:
r = CMP(major(a->devnum), major(b->devnum));
if (r != 0)
return r;
return CMP(minor(a->devnum), minor(b->devnum));
default:
;
}
@@ -1882,6 +1906,22 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
break;
}
case TABLE_MODE_INODE_TYPE:
if (d->mode == MODE_INVALID)
return table_ersatz_string(t);
return inode_type_to_string(d->mode);
case TABLE_DEVNUM:
if (devnum_is_zero(d->devnum))
return table_ersatz_string(t);
if (asprintf(&d->formatted, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)) < 0)
return NULL;
break;
default:
assert_not_reached();
}
@@ -2696,11 +2736,20 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
return json_variant_new_integer(ret, d->int_val);
case TABLE_MODE:
case TABLE_MODE_INODE_TYPE:
if (d->mode == MODE_INVALID)
return json_variant_new_null(ret);
return json_variant_new_unsigned(ret, d->mode);
case TABLE_DEVNUM:
if (devnum_is_zero(d->devnum))
return json_variant_new_null(ret);
return json_build(ret, JSON_BUILD_ARRAY(
JSON_BUILD_UNSIGNED(major(d->devnum)),
JSON_BUILD_UNSIGNED(minor(d->devnum))));
default:
return -EINVAL;
}
+3 -1
View File
@@ -51,7 +51,9 @@ typedef enum TableDataType {
TABLE_GID,
TABLE_PID,
TABLE_SIGNAL,
TABLE_MODE, /* as in UNIX file mode (mode_t), in typical octal output */
TABLE_MODE, /* as in UNIX file mode (mode_t), in typical octal output */
TABLE_MODE_INODE_TYPE, /* also mode_t, but displays only the inode type as string */
TABLE_DEVNUM, /* a dev_t, displayed in the usual major:minor way */
_TABLE_DATA_TYPE_MAX,
/* The following are not really data types, but commands for table_add_cell_many() to make changes to
+1 -3
View File
@@ -334,11 +334,9 @@ int lock_main(int argc, char *argv[], void *userdata) {
if (fd < 0)
return fd;
r = fdset_put(fds, fd);
r = fdset_consume(fds, TAKE_FD(fd));
if (r < 0)
return log_oom();
TAKE_FD(fd);
}
}
+27
View File
@@ -48,5 +48,32 @@ assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all"
rm /tmp/syncfifo1 /tmp/syncfifo2
MYSCRIPT="/tmp/myscript$RANDOM.sh"
cat >> "$MYSCRIPT" <<'EOF'
#!/usr/bin/env bash
set -eux
set -o pipefail
N="/tmp/$RANDOM"
echo $RANDOM > "$N"
systemd-notify --fd=4 --fdname=quux --pid=parent 4< "$N"
rm "$N"
systemd-notify --ready
exec sleep infinity
EOF
chmod +x "$MYSCRIPT"
MYUNIT="myunit$RANDOM.service"
systemd-run -u "$MYUNIT" -p Type=notify -p StandardOutput=journal+console -p StandardError=journal+console -p FileDescriptorStoreMax=7 "$MYSCRIPT"
test "$(systemd-analyze fdstore "$MYUNIT" | wc -l)" -eq 2
systemd-analyze fdstore "$MYUNIT" --json=short
systemd-analyze fdstore "$MYUNIT" --json=short | grep -P -q '\[{"fdname":"quux","type":.*,"devno":\[.*\],"inode":.*,"rdevno":null,"path":"/tmp/.*","flags":"ro"}\]'
systemctl stop "$MYUNIT"
rm "$MYSCRIPT"
touch /testok
rm /failed
exit 0