From d83ce136367a36da3671c94cfe549549f99e4167 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 27 Mar 2023 18:08:27 +0200 Subject: [PATCH 01/13] stat-util: add inode_type_to_string() helper for showing mode_t inode type as string --- src/basic/stat-util.c | 23 +++++++++++++++++++++++ src/basic/stat-util.h | 2 ++ 2 files changed, 25 insertions(+) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 6eaa3da459..2b09ab63b2 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -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; +} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 24684d5794..726d0644aa 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -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); From d80e2a1ed36cd2fe6f5b6bda9419246289ef2897 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 27 Mar 2023 18:11:18 +0200 Subject: [PATCH 02/13] devnum-util: add new devnum_is_zero() helper --- src/basic/devnum-util.c | 2 +- src/basic/devnum-util.h | 4 ++++ src/basic/terminal-util.c | 2 +- src/libsystemd/sd-device/device-util.c | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/basic/devnum-util.c b/src/basic/devnum-util.c index de02255eef..f82e13bdb0 100644 --- a/src/basic/devnum-util.c +++ b/src/basic/devnum-util.c @@ -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); diff --git a/src/basic/devnum-util.h b/src/basic/devnum-util.h index 38aa4efa87..e109de9913 100644 --- a/src/basic/devnum-util.h +++ b/src/basic/devnum-util.h @@ -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; +} diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index d3aa5c27e2..796c262acf 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -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) diff --git a/src/libsystemd/sd-device/device-util.c b/src/libsystemd/sd-device/device-util.c index 47c5d98896..529eff2fd1 100644 --- a/src/libsystemd/sd-device/device-util.c +++ b/src/libsystemd/sd-device/device-util.c @@ -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); From b2b84f4b236f371d169f470e5239815c3cd01890 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 27 Mar 2023 21:57:53 +0200 Subject: [PATCH 03/13] fd-util: add helper for converting O_RDONLY/WRONLY/RDWR to strings --- src/basic/fd-util.c | 13 +++++++++++++ src/basic/fd-util.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index b968bf948c..bea9a93ff5 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -935,3 +935,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; + } +} diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index a6353cf48e..91f3d7fe9d 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -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); From bd33207bb11ad3d7c704e23b3860338fcfaab98b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 27 Mar 2023 18:14:02 +0200 Subject: [PATCH 04/13] format-table: add inode type cell type --- src/shared/format-table.c | 12 ++++++++++++ src/shared/format-table.h | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 351a5ede11..43e6510c4e 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -24,6 +24,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" @@ -348,6 +349,7 @@ 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); default: @@ -1022,6 +1024,7 @@ 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; @@ -1377,6 +1380,7 @@ 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); default: @@ -1882,6 +1886,13 @@ 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); + default: assert_not_reached(); } @@ -2696,6 +2707,7 @@ 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); diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 5a2b366b59..dbed6cdee9 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -51,7 +51,8 @@ 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_DATA_TYPE_MAX, /* The following are not really data types, but commands for table_add_cell_many() to make changes to From 522f12224c5118ca1924a2d35a647d04a8e73fdd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 27 Mar 2023 18:14:39 +0200 Subject: [PATCH 05/13] format-table: add new cell type for displaying major/minor devnums --- src/shared/format-table.c | 37 +++++++++++++++++++++++++++++++++++++ src/shared/format-table.h | 1 + 2 files changed, 38 insertions(+) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 43e6510c4e..204e8b68b6 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -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" @@ -108,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; @@ -352,6 +354,9 @@ static size_t table_data_size(TableDataType type, const void *data) { case TABLE_MODE_INODE_TYPE: return sizeof(mode_t); + case TABLE_DEVNUM: + return sizeof(dev_t); + default: assert_not_reached(); } @@ -869,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) { @@ -1029,6 +1035,11 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { 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); @@ -1276,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); @@ -1383,6 +1396,13 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t 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: ; } @@ -1893,6 +1913,15 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas 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(); } @@ -2713,6 +2742,14 @@ static int table_data_to_json(TableData *d, JsonVariant **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; } diff --git a/src/shared/format-table.h b/src/shared/format-table.h index dbed6cdee9..148418c70f 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -53,6 +53,7 @@ typedef enum TableDataType { TABLE_SIGNAL, 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 From 2ea24611b99d12955ba374f072148b9ad6d644dc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 27 Mar 2023 18:16:40 +0200 Subject: [PATCH 06/13] pid1: add DumpFileDescriptorStore() bus call that returns fdstore content info --- man/org.freedesktop.systemd1.xml | 22 +++++++ src/basic/missing_fcntl.h | 9 +++ src/core/dbus-manager.c | 9 +++ src/core/dbus-service.c | 74 +++++++++++++++++++++++ src/core/dbus-service.h | 1 + src/libsystemd/sd-bus/bus-common-errors.c | 2 + src/libsystemd/sd-bus/bus-common-errors.h | 2 + 7 files changed, 119 insertions(+) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index ab94a90b7c..1a82a19bbd 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -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 { + + @@ -1531,6 +1535,11 @@ node /org/freedesktop/systemd1 { New Control Group Interface for more information how to make use of this functionality for resource control purposes. + + DumpUnitFileDescriptorStore() returns an array with information about the + file descriptors currently in the file descriptor store of the specified unit. This call is equivalent + to DumpFileDescriptorStore() on the + org.freedesktop.systemd1.Service. For further details, see below. @@ -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 { + + @@ -4393,6 +4405,16 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { directly on the Manager object has the advantage of not requiring a GetUnit() call to get the unit object for a specific unit name. Calling the methods on the Manager object is hence a round trip optimization. + + DumpFileDescriptorStore() 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 FDNAME= field), the file descriptor inode type and access + mode as integer (i.e. a mode_t value, flags such as S_IFREG, + S_IRUSR, …), 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. O_RDWR, O_RDONLY, + …). diff --git a/src/basic/missing_fcntl.h b/src/basic/missing_fcntl.h index 00937d2af0..79e95a8f6f 100644 --- a/src/basic/missing_fcntl.h +++ b/src/basic/missing_fcntl.h @@ -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 diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index c88ef93443..2cb9efc14e 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -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), diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index ddfffe80fd..742893370f 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -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), diff --git a/src/core/dbus-service.h b/src/core/dbus-service.h index 9a05465802..aea6cf77f3 100644 --- a/src/core/dbus-service.h +++ b/src/core/dbus-service.h @@ -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); diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 0eaedec87c..df26fd75cd 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -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), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index b6c2e93ea5..3a0eef49ef 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -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" From 5f43c97cd24b25f846d005eccd7837a2cd279230 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 27 Mar 2023 18:16:03 +0200 Subject: [PATCH 07/13] analyze: add new fdstore verb --- man/systemd-analyze.xml | 37 +++++++++++- man/systemd.service.xml | 13 +++- src/analyze/analyze-fdstore.c | 110 ++++++++++++++++++++++++++++++++++ src/analyze/analyze-fdstore.h | 5 ++ src/analyze/analyze.c | 9 ++- src/analyze/meson.build | 1 + 6 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 src/analyze/analyze-fdstore.c create mode 100644 src/analyze/analyze-fdstore.h diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index ad6d691a1d..9fd28e6f45 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -156,6 +156,12 @@ malloc D-BUS SERVICE + + systemd-analyze + OPTIONS + fdstore + UNIT + @@ -803,8 +809,37 @@ $ systemd-analyze verify /tmp/source:alias.service } - + + + <command>systemd-analyze fdstore <optional><replaceable>UNIT</replaceable>...</optional></command> + + 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 FileDescriptorStoreMax= enabled, see + systemd.service5 for + details. + + + Table output + $ 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 +… + + + 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 + .st_dev and .st_rdev fields in struct stat (see + stat2 for + details). The listed inode numbers in the "INODE" column are on the file system indicated by + "DEVNO". + + diff --git a/man/systemd.service.xml b/man/systemd.service.xml index e8be2ff468..665128ee77 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -1141,7 +1141,18 @@ fully stopped and no job is queued or being executed for it. If this option is used, NotifyAccess= (see above) should be set to open access to the notification socket provided by systemd. If NotifyAccess= is not set, it will be implicitly set to - . + . + + The fdstore command of + systemd-analyze1 + may be used to list the current contents of a service's file descriptor store. + + 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. diff --git a/src/analyze/analyze-fdstore.c b/src/analyze/analyze-fdstore.c new file mode 100644 index 0000000000..13db7f5088 --- /dev/null +++ b/src/analyze/analyze-fdstore.c @@ -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; +} diff --git a/src/analyze/analyze-fdstore.h b/src/analyze/analyze-fdstore.h new file mode 100644 index 0000000000..0b990db395 --- /dev/null +++ b/src/analyze/analyze-fdstore.h @@ -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); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 12c0bd653f..0246da4b45 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -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 }, {} }; diff --git a/src/analyze/meson.build b/src/analyze/meson.build index ac40600a6d..695089a0be 100644 --- a/src/analyze/meson.build +++ b/src/analyze/meson.build @@ -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', From 08a0ebc6beef91c54931160b43c10cbc610f7533 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Mar 2023 10:26:56 +0200 Subject: [PATCH 08/13] fdset: minor modernizations Let's use more _cleanup_ expressions. Various other modernizations. No actual code changes, except for maybe a conversion to use heap memory when generating an array of fds, instead of stack as before. Given that fdstores are typically user controlled, that should be a wise idea. --- src/shared/fdset.c | 101 ++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 56 deletions(-) diff --git a/src/shared/fdset.c b/src/shared/fdset.c index cfa12a509e..4427c1ce68 100644 --- a/src/shared/fdset.c +++ b/src/shared/fdset.c @@ -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; } @@ -78,7 +81,8 @@ int fdset_put(FDSet *s, int fd) { } 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 +92,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 +112,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,46 +169,40 @@ 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) { - void *e; - int *a = NULL; + _cleanup_free_ int *a = NULL; size_t j = 0, m; + void *e; m = fdset_size(fds); - if (m > 0) { - a = newa(int, m); + a = new(int, m); + if (!a) + return -ENOMEM; + SET_FOREACH(e, MAKE_SET(fds)) a[j++] = PTR_TO_FD(e); } From bdcad22e8e508308032cba5f8c2d5c093adb7d87 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Mar 2023 11:17:23 +0200 Subject: [PATCH 09/13] fdset: add new helper to convert an fdset to an array --- src/shared/fdset.c | 41 ++++++++++++++++++++++++++++++----------- src/shared/fdset.h | 2 ++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/shared/fdset.c b/src/shared/fdset.c index 4427c1ce68..49928b66d6 100644 --- a/src/shared/fdset.c +++ b/src/shared/fdset.c @@ -192,24 +192,43 @@ int fdset_new_listen_fds(FDSet **ret, bool unset) { return 0; } -int fdset_close_others(FDSet *fds) { - _cleanup_free_ int *a = NULL; - size_t j = 0, m; +int fdset_to_array(FDSet *fds, int **ret) { + unsigned j = 0, m; void *e; + int *a; + + assert(ret); m = fdset_size(fds); - if (m > 0) { - a = new(int, m); - if (!a) - return -ENOMEM; - - 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) { diff --git a/src/shared/fdset.h b/src/shared/fdset.h index 39d15ee4aa..0e55db81b4 100644 --- a/src/shared/fdset.h +++ b/src/shared/fdset.h @@ -24,6 +24,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); From e829f28c1bc6e6865261bfb3bc26089f50e0c7bd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 29 Mar 2023 18:52:25 +0200 Subject: [PATCH 10/13] fdset: add new fdset_consume() helper --- src/shared/fdset.c | 13 +++++++++++++ src/shared/fdset.h | 1 + src/udev/udevadm-lock.c | 4 +--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/shared/fdset.c b/src/shared/fdset.c index 49928b66d6..d816a3e4ef 100644 --- a/src/shared/fdset.c +++ b/src/shared/fdset.c @@ -80,6 +80,19 @@ 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) { _cleanup_close_ int copy = -EBADF; int r; diff --git a/src/shared/fdset.h b/src/shared/fdset.h index 0e55db81b4..9700cdbbca 100644 --- a/src/shared/fdset.h +++ b/src/shared/fdset.h @@ -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); diff --git a/src/udev/udevadm-lock.c b/src/udev/udevadm-lock.c index d19e7561f8..9a4a8adbc3 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -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); } } From 6e4a324574d5f1b2296799324dbdb54326078233 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Mar 2023 11:17:44 +0200 Subject: [PATCH 11/13] notify: add support for sending fds with notification messages This exposes the fd passing we support via sd_pid_notify_with_fds() also via the command line tool systemd-notify. --- man/systemd-notify.xml | 23 +++++++++ src/notify/notify.c | 109 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 128 insertions(+), 4 deletions(-) diff --git a/man/systemd-notify.xml b/man/systemd-notify.xml index 5a154686f5..1b469fe85c 100644 --- a/man/systemd-notify.xml +++ b/man/systemd-notify.xml @@ -191,6 +191,29 @@ escaped as \;. + + + + Send a file descriptor along with the notification message. This is useful when + invoked in services that have the FileDescriptorStoreMax= setting enabled, see + systemd.service5 + for details. The specified file descriptor must be passed to systemd-notify when + invoked. This option may be used multiple times to pass multiple file descriptors in a single + notification message. + + To use this functionality from a bash shell, use an expression like the following: + systemd-notify --fd=4 --fd=5 4</some/file 5</some/other/file + + + + + + Set a name to assign to the file descriptors passed via (see + above). This controls the FDNAME= 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. + + diff --git a/src/notify/notify.c b/src/notify/notify.c index 7320ffebde..8489b83873 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -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) From b0717f1b82774f023c0bfee243b9466ab5fcce4c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Mar 2023 18:28:15 +0200 Subject: [PATCH 12/13] test: add test case for fdstore dumping logic, and systemd-notify --fd= --- test/units/testsuite-80.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/units/testsuite-80.sh b/test/units/testsuite-80.sh index 43647a707f..0072133cd4 100755 --- a/test/units/testsuite-80.sh +++ b/test/units/testsuite-80.sh @@ -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 From 3739c2fdfc9d01cd5dd5f349fdc5cea1561517f1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 28 Mar 2023 11:39:44 +0200 Subject: [PATCH 13/13] update TODO --- TODO | 4 ---- 1 file changed, 4 deletions(-) diff --git a/TODO b/TODO index 2c0d185683..a6ca119531 100644 --- a/TODO +++ b/TODO @@ -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