From 9282f93f70cc8015266d94000fb7804fed969af8 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 22 Jan 2025 18:45:03 +0100 Subject: [PATCH] systemd: update code from upstream (2025-01-22) This is a direct dump from systemd git. $ git clean -fdx && \ git cat-file -p HEAD | sed '1,/^======$/ d' | bash - && \ git add . ====== SYSTEMD_DIR=../systemd COMMIT=e8908d2fc180f5a98dd37bfbc9c5952de5f18899 ( cd "$SYSTEMD_DIR" git checkout "$COMMIT" git reset --hard git clean -fdx ) git ls-files -z :/src/libnm-systemd-core/src/ \ :/src/libnm-systemd-shared/src/ \ :/src/libnm-std-aux/unaligned-fundamental.h \ :/src/libnm-std-aux/unaligned.h | \ xargs -0 rm -f nm_copy_sd_shared() { mkdir -p "./src/libnm-systemd-shared/$(dirname "$1")" cp "$SYSTEMD_DIR/$1" "./src/libnm-systemd-shared/$1" } nm_copy_sd_core() { mkdir -p "./src/libnm-systemd-core/$(dirname "$1")" cp "$SYSTEMD_DIR/$1" "./src/libnm-systemd-core/$1" } nm_copy_sd_stdaux() { mkdir -p "./src/libnm-std-aux/" cp "$SYSTEMD_DIR/$1" "./src/libnm-std-aux/${1##*/}" } nm_copy_sd_core "src/libsystemd-network/dhcp-duid-internal.h" nm_copy_sd_core "src/libsystemd-network/dhcp6-client-internal.h" nm_copy_sd_core "src/libsystemd-network/dhcp6-internal.h" nm_copy_sd_core "src/libsystemd-network/dhcp6-lease-internal.h" nm_copy_sd_core "src/libsystemd-network/dhcp6-network.c" nm_copy_sd_core "src/libsystemd-network/dhcp6-option.c" nm_copy_sd_core "src/libsystemd-network/dhcp6-option.h" nm_copy_sd_core "src/libsystemd-network/dhcp6-protocol.c" nm_copy_sd_core "src/libsystemd-network/dhcp6-protocol.h" nm_copy_sd_core "src/libsystemd-network/network-common.c" nm_copy_sd_core "src/libsystemd-network/network-common.h" nm_copy_sd_core "src/libsystemd-network/sd-dhcp-duid.c" nm_copy_sd_core "src/libsystemd-network/sd-dhcp6-client.c" nm_copy_sd_core "src/libsystemd-network/sd-dhcp6-lease.c" nm_copy_sd_core "src/libsystemd/sd-device/device-internal.h" nm_copy_sd_core "src/libsystemd/sd-device/device-private.c" nm_copy_sd_core "src/libsystemd/sd-device/device-private.h" nm_copy_sd_core "src/libsystemd/sd-device/device-util.h" nm_copy_sd_core "src/libsystemd/sd-device/sd-device.c" nm_copy_sd_core "src/libsystemd/sd-event/event-source.h" nm_copy_sd_core "src/libsystemd/sd-event/event-util.c" nm_copy_sd_core "src/libsystemd/sd-event/event-util.h" nm_copy_sd_core "src/libsystemd/sd-event/sd-event.c" nm_copy_sd_core "src/libsystemd/sd-id128/id128-util.c" nm_copy_sd_core "src/libsystemd/sd-id128/id128-util.h" nm_copy_sd_core "src/libsystemd/sd-id128/sd-id128.c" nm_copy_sd_core "src/systemd/_sd-common.h" nm_copy_sd_core "src/systemd/sd-device.h" nm_copy_sd_core "src/systemd/sd-dhcp-duid.h" nm_copy_sd_core "src/systemd/sd-dhcp6-client.h" nm_copy_sd_core "src/systemd/sd-dhcp6-lease.h" nm_copy_sd_core "src/systemd/sd-dhcp6-option.h" nm_copy_sd_core "src/systemd/sd-dhcp6-protocol.h" nm_copy_sd_core "src/systemd/sd-event.h" nm_copy_sd_core "src/systemd/sd-id128.h" nm_copy_sd_core "src/systemd/sd-ndisc.h" nm_copy_sd_shared "src/basic/alloc-util.c" nm_copy_sd_shared "src/basic/alloc-util.h" nm_copy_sd_shared "src/basic/arphrd-util.h" nm_copy_sd_shared "src/basic/bitfield.h" nm_copy_sd_shared "src/basic/btrfs.c" nm_copy_sd_shared "src/basic/btrfs.h" nm_copy_sd_shared "src/basic/cgroup-util.h" nm_copy_sd_shared "src/basic/chase.h" nm_copy_sd_shared "src/basic/chattr-util.c" nm_copy_sd_shared "src/basic/chattr-util.h" nm_copy_sd_shared "src/basic/constants.h" nm_copy_sd_shared "src/basic/devnum-util.c" nm_copy_sd_shared "src/basic/devnum-util.h" nm_copy_sd_shared "src/basic/dns-def.h" nm_copy_sd_shared "src/basic/env-file.c" nm_copy_sd_shared "src/basic/env-file.h" nm_copy_sd_shared "src/basic/env-util.c" nm_copy_sd_shared "src/basic/env-util.h" nm_copy_sd_shared "src/basic/errno-util.h" nm_copy_sd_shared "src/basic/escape.c" nm_copy_sd_shared "src/basic/escape.h" nm_copy_sd_shared "src/basic/ether-addr-util.c" nm_copy_sd_shared "src/basic/ether-addr-util.h" nm_copy_sd_shared "src/basic/extract-word.c" nm_copy_sd_shared "src/basic/extract-word.h" nm_copy_sd_shared "src/basic/fd-util.c" nm_copy_sd_shared "src/basic/fd-util.h" nm_copy_sd_shared "src/basic/fileio.c" nm_copy_sd_shared "src/basic/fileio.h" nm_copy_sd_shared "src/basic/format-ifname.c" nm_copy_sd_shared "src/basic/format-ifname.h" nm_copy_sd_shared "src/basic/format-util.c" nm_copy_sd_shared "src/basic/format-util.h" nm_copy_sd_shared "src/basic/fs-util.c" nm_copy_sd_shared "src/basic/fs-util.h" nm_copy_sd_shared "src/basic/glyph-util.c" nm_copy_sd_shared "src/basic/glyph-util.h" nm_copy_sd_shared "src/basic/hash-funcs.c" nm_copy_sd_shared "src/basic/hash-funcs.h" nm_copy_sd_shared "src/basic/hashmap.c" nm_copy_sd_shared "src/basic/hashmap.h" nm_copy_sd_shared "src/basic/hexdecoct.c" nm_copy_sd_shared "src/basic/hexdecoct.h" nm_copy_sd_shared "src/basic/hostname-util.c" nm_copy_sd_shared "src/basic/hostname-util.h" nm_copy_sd_shared "src/basic/in-addr-util.c" nm_copy_sd_shared "src/basic/in-addr-util.h" nm_copy_sd_shared "src/basic/inotify-util.c" nm_copy_sd_shared "src/basic/inotify-util.h" nm_copy_sd_shared "src/basic/io-util.c" nm_copy_sd_shared "src/basic/io-util.h" nm_copy_sd_shared "src/basic/iovec-util.h" nm_copy_sd_shared "src/basic/label.c" nm_copy_sd_shared "src/basic/label.h" nm_copy_sd_shared "src/basic/list.h" nm_copy_sd_shared "src/basic/locale-util.c" nm_copy_sd_shared "src/basic/locale-util.h" nm_copy_sd_shared "src/basic/lock-util.h" nm_copy_sd_shared "src/basic/log.h" nm_copy_sd_shared "src/basic/macro.h" nm_copy_sd_shared "src/basic/memory-util.c" nm_copy_sd_shared "src/basic/memory-util.h" nm_copy_sd_shared "src/basic/mempool.c" nm_copy_sd_shared "src/basic/mempool.h" nm_copy_sd_shared "src/basic/missing_fcntl.h" nm_copy_sd_shared "src/basic/missing_pidfd.h" nm_copy_sd_shared "src/basic/missing_random.h" nm_copy_sd_shared "src/basic/missing_socket.h" nm_copy_sd_shared "src/basic/missing_stat.h" nm_copy_sd_shared "src/basic/missing_syscall.h" nm_copy_sd_shared "src/basic/missing_threads.h" nm_copy_sd_shared "src/basic/missing_type.h" nm_copy_sd_shared "src/basic/missing_wait.h" nm_copy_sd_shared "src/basic/namespace-util.h" nm_copy_sd_shared "src/basic/ordered-set.c" nm_copy_sd_shared "src/basic/ordered-set.h" nm_copy_sd_shared "src/basic/origin-id.h" nm_copy_sd_shared "src/basic/parse-util.c" nm_copy_sd_shared "src/basic/parse-util.h" nm_copy_sd_shared "src/basic/path-util.c" nm_copy_sd_shared "src/basic/path-util.h" nm_copy_sd_shared "src/basic/pidfd-util.c" nm_copy_sd_shared "src/basic/pidfd-util.h" nm_copy_sd_shared "src/basic/pidref.h" nm_copy_sd_shared "src/basic/prioq.c" nm_copy_sd_shared "src/basic/prioq.h" nm_copy_sd_shared "src/basic/process-util.c" nm_copy_sd_shared "src/basic/process-util.h" nm_copy_sd_shared "src/basic/random-util.c" nm_copy_sd_shared "src/basic/random-util.h" nm_copy_sd_shared "src/basic/ratelimit.c" nm_copy_sd_shared "src/basic/ratelimit.h" nm_copy_sd_shared "src/basic/set.h" nm_copy_sd_shared "src/basic/sha256.c" nm_copy_sd_shared "src/basic/sha256.h" nm_copy_sd_shared "src/basic/signal-util.c" nm_copy_sd_shared "src/basic/signal-util.h" nm_copy_sd_shared "src/basic/siphash24.h" nm_copy_sd_shared "src/basic/socket-util.c" nm_copy_sd_shared "src/basic/socket-util.h" nm_copy_sd_shared "src/basic/sort-util.h" nm_copy_sd_shared "src/basic/sparse-endian.h" nm_copy_sd_shared "src/basic/stat-util.c" nm_copy_sd_shared "src/basic/stat-util.h" nm_copy_sd_shared "src/basic/stdio-util.h" nm_copy_sd_shared "src/basic/string-table.c" nm_copy_sd_shared "src/basic/string-table.h" nm_copy_sd_shared "src/basic/string-util.c" nm_copy_sd_shared "src/basic/string-util.h" nm_copy_sd_shared "src/basic/strv.c" nm_copy_sd_shared "src/basic/strv.h" nm_copy_sd_shared "src/basic/strxcpyx.c" nm_copy_sd_shared "src/basic/strxcpyx.h" nm_copy_sd_shared "src/basic/time-util.c" nm_copy_sd_shared "src/basic/time-util.h" nm_copy_sd_shared "src/basic/tmpfile-util.c" nm_copy_sd_shared "src/basic/tmpfile-util.h" nm_copy_sd_shared "src/basic/umask-util.h" nm_copy_sd_shared "src/basic/user-util.h" nm_copy_sd_shared "src/basic/utf8.c" nm_copy_sd_shared "src/basic/utf8.h" nm_copy_sd_shared "src/fundamental/iovec-util-fundamental.h" nm_copy_sd_shared "src/fundamental/logarithm.h" nm_copy_sd_shared "src/fundamental/macro-fundamental.h" nm_copy_sd_shared "src/fundamental/memory-util-fundamental.h" nm_copy_sd_shared "src/fundamental/sha256-fundamental.c" nm_copy_sd_shared "src/fundamental/sha256-fundamental.h" nm_copy_sd_shared "src/fundamental/string-util-fundamental.c" nm_copy_sd_shared "src/fundamental/string-util-fundamental.h" nm_copy_sd_shared "src/shared/dns-domain.c" nm_copy_sd_shared "src/shared/dns-domain.h" nm_copy_sd_shared "src/shared/log-link.h" nm_copy_sd_shared "src/shared/web-util.c" nm_copy_sd_shared "src/shared/web-util.h" nm_copy_sd_stdaux "src/basic/unaligned.h" nm_copy_sd_stdaux "src/fundamental/unaligned-fundamental.h" --- .../libsystemd-network/dhcp-duid-internal.h | 2 +- .../src/libsystemd-network/dhcp6-internal.h | 5 +- .../libsystemd-network/dhcp6-lease-internal.h | 3 + .../src/libsystemd-network/dhcp6-network.c | 18 +- .../src/libsystemd-network/dhcp6-option.c | 77 +-- .../src/libsystemd-network/dhcp6-protocol.h | 14 +- .../src/libsystemd-network/network-common.c | 2 +- .../src/libsystemd-network/network-internal.h | 31 - .../src/libsystemd-network/sd-dhcp6-client.c | 11 +- .../src/libsystemd-network/sd-dhcp6-lease.c | 117 +++- .../src/libsystemd/sd-device/device-private.c | 4 +- .../src/libsystemd/sd-device/device-private.h | 8 +- .../src/libsystemd/sd-device/device-util.h | 2 +- .../src/libsystemd/sd-device/sd-device.c | 178 ++++-- .../src/libsystemd/sd-event/event-source.h | 3 + .../src/libsystemd/sd-event/event-util.c | 9 + .../src/libsystemd/sd-event/event-util.h | 2 + .../src/libsystemd/sd-event/sd-event.c | 265 ++++---- .../src/libsystemd/sd-id128/id128-util.c | 67 +- .../src/libsystemd/sd-id128/id128-util.h | 3 + .../src/libsystemd/sd-id128/sd-id128.c | 52 +- .../src/systemd/_sd-common.h | 11 + .../src/systemd/sd-device.h | 30 +- .../src/systemd/sd-dhcp-duid.h | 1 - .../src/systemd/sd-dhcp6-lease.h | 2 + .../src/systemd/sd-dhcp6-protocol.h | 5 +- src/libnm-systemd-core/src/systemd/sd-event.h | 27 +- src/libnm-systemd-core/src/systemd/sd-id128.h | 15 +- src/libnm-systemd-core/src/systemd/sd-ndisc.h | 97 +-- .../src/basic/alloc-util.c | 11 +- .../src/basic/alloc-util.h | 19 +- .../src/basic/arphrd-util.h | 2 +- src/libnm-systemd-shared/src/basic/bitfield.h | 73 +++ .../src/basic/cgroup-util.h | 48 +- src/libnm-systemd-shared/src/basic/chase.h | 8 +- .../src/basic/chattr-util.c | 174 ++++++ .../src/basic/chattr-util.h | 64 ++ .../src/basic/constants.h | 14 +- .../src/basic/devnum-util.c | 11 +- src/libnm-systemd-shared/src/basic/env-util.c | 85 ++- src/libnm-systemd-shared/src/basic/env-util.h | 17 +- .../src/basic/errno-util.h | 11 +- src/libnm-systemd-shared/src/basic/escape.c | 12 +- .../src/basic/ether-addr-util.c | 2 +- .../src/basic/ether-addr-util.h | 4 +- .../src/basic/extract-word.c | 35 +- .../src/basic/extract-word.h | 5 +- src/libnm-systemd-shared/src/basic/fd-util.c | 263 ++++++-- src/libnm-systemd-shared/src/basic/fd-util.h | 20 +- src/libnm-systemd-shared/src/basic/fileio.c | 245 +++++--- src/libnm-systemd-shared/src/basic/fileio.h | 52 +- .../src/basic/format-ifname.c | 37 ++ .../src/basic/format-ifname.h | 27 + .../src/basic/format-util.c | 51 +- .../src/basic/format-util.h | 36 +- src/libnm-systemd-shared/src/basic/fs-util.c | 377 +++++++++--- src/libnm-systemd-shared/src/basic/fs-util.h | 58 +- .../src/basic/glyph-util.c | 19 +- .../src/basic/glyph-util.h | 16 +- src/libnm-systemd-shared/src/basic/hashmap.c | 73 ++- src/libnm-systemd-shared/src/basic/hashmap.h | 15 +- .../src/basic/hexdecoct.c | 25 +- .../src/basic/hexdecoct.h | 4 +- .../src/basic/in-addr-util.c | 109 ++-- .../src/basic/in-addr-util.h | 23 +- .../src/basic/inotify-util.c | 14 +- .../src/basic/iovec-util.h | 35 +- src/libnm-systemd-shared/src/basic/label.c | 11 +- src/libnm-systemd-shared/src/basic/label.h | 6 +- .../src/basic/locale-util.c | 10 +- .../src/basic/lock-util.h | 4 +- src/libnm-systemd-shared/src/basic/log.h | 63 +- src/libnm-systemd-shared/src/basic/macro.h | 24 - .../src/basic/memory-util.h | 11 +- .../src/basic/missing_fcntl.h | 29 +- .../src/basic/missing_pidfd.h | 48 ++ .../src/basic/missing_random.h | 16 +- .../src/basic/missing_socket.h | 53 +- .../src/basic/missing_syscall.h | 97 +-- .../src/basic/missing_threads.h | 4 +- .../src/basic/missing_type.h | 4 +- .../src/basic/missing_wait.h | 12 + .../src/basic/namespace-util.h | 49 +- .../src/basic/parse-util.c | 19 +- .../src/basic/parse-util.h | 2 - .../src/basic/path-util.c | 133 ++-- .../src/basic/path-util.h | 62 +- .../src/basic/pidfd-util.c | 275 +++++++++ .../src/basic/pidfd-util.h | 21 + src/libnm-systemd-shared/src/basic/pidref.h | 74 ++- src/libnm-systemd-shared/src/basic/prioq.c | 25 +- .../src/basic/process-util.c | 576 ++++++++++-------- .../src/basic/process-util.h | 88 ++- .../src/basic/random-util.c | 120 ++-- .../src/basic/random-util.h | 2 + .../src/basic/ratelimit.c | 28 +- .../src/basic/ratelimit.h | 6 +- src/libnm-systemd-shared/src/basic/sha256.c | 50 ++ src/libnm-systemd-shared/src/basic/sha256.h | 16 + .../src/basic/signal-util.c | 67 +- .../src/basic/signal-util.h | 28 +- .../src/basic/socket-util.c | 99 ++- .../src/basic/socket-util.h | 15 +- .../src/basic/stat-util.c | 341 +++++++---- .../src/basic/stat-util.h | 50 +- .../src/basic/stdio-util.h | 4 +- .../src/basic/string-table.h | 13 +- .../src/basic/string-util.c | 288 ++++----- .../src/basic/string-util.h | 120 ++-- src/libnm-systemd-shared/src/basic/strv.c | 284 ++++++++- src/libnm-systemd-shared/src/basic/strv.h | 18 +- .../src/basic/time-util.c | 186 +++--- .../src/basic/time-util.h | 18 +- .../src/basic/tmpfile-util.c | 63 +- .../src/basic/tmpfile-util.h | 2 + .../src/basic/user-util.h | 34 +- src/libnm-systemd-shared/src/basic/utf8.c | 77 +-- src/libnm-systemd-shared/src/basic/utf8.h | 23 +- .../src/fundamental/iovec-util-fundamental.h | 37 ++ .../src/fundamental/macro-fundamental.h | 83 ++- .../{sha256.c => sha256-fundamental.c} | 10 +- .../{sha256.h => sha256-fundamental.h} | 0 .../src/shared/dns-domain.c | 155 ++++- .../src/shared/dns-domain.h | 2 + 124 files changed, 4724 insertions(+), 2386 deletions(-) delete mode 100644 src/libnm-systemd-core/src/libsystemd-network/network-internal.h create mode 100644 src/libnm-systemd-shared/src/basic/bitfield.h create mode 100644 src/libnm-systemd-shared/src/basic/chattr-util.c create mode 100644 src/libnm-systemd-shared/src/basic/chattr-util.h create mode 100644 src/libnm-systemd-shared/src/basic/format-ifname.c create mode 100644 src/libnm-systemd-shared/src/basic/format-ifname.h create mode 100644 src/libnm-systemd-shared/src/basic/missing_pidfd.h create mode 100644 src/libnm-systemd-shared/src/basic/missing_wait.h create mode 100644 src/libnm-systemd-shared/src/basic/pidfd-util.c create mode 100644 src/libnm-systemd-shared/src/basic/pidfd-util.h create mode 100644 src/libnm-systemd-shared/src/basic/sha256.c create mode 100644 src/libnm-systemd-shared/src/basic/sha256.h create mode 100644 src/libnm-systemd-shared/src/fundamental/iovec-util-fundamental.h rename src/libnm-systemd-shared/src/fundamental/{sha256.c => sha256-fundamental.c} (98%) rename src/libnm-systemd-shared/src/fundamental/{sha256.h => sha256-fundamental.h} (100%) diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-duid-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-duid-internal.h index f8bc15c47e..0d3d6b5cef 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-duid-internal.h +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-duid-internal.h @@ -73,7 +73,7 @@ static inline bool duid_data_size_is_valid(size_t size) { return size >= MIN_DUID_DATA_LEN && size <= MAX_DUID_DATA_LEN; } -const char *duid_type_to_string(DUIDType t) _const_; +const char* duid_type_to_string(DUIDType t) _const_; int dhcp_duid_to_string_internal(uint16_t type, const void *data, size_t data_size, char **ret); int dhcp_identifier_set_iaid( diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h index 3fbfc028e9..ecd62ea802 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h @@ -84,9 +84,8 @@ struct sd_dhcp6_client { bool send_release; }; -int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address); -int dhcp6_network_send_udp_socket(int s, struct in6_addr *address, - const void *packet, size_t len); +int dhcp6_network_bind_udp_socket(int ifindex, const struct in6_addr *address); +int dhcp6_network_send_udp_socket(int s, const struct in6_addr *address, const void *packet, size_t len); int dhcp6_client_send_message(sd_dhcp6_client *client); int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id); diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h index e76a108f60..60cd84f2d8 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-lease-internal.h @@ -8,6 +8,7 @@ #include #include "sd-dhcp6-lease.h" +#include "dns-resolver-internal.h" #include "dhcp6-option.h" #include "dhcp6-protocol.h" @@ -38,6 +39,8 @@ struct sd_dhcp6_lease { struct in6_addr *dns; size_t dns_count; + sd_dns_resolver *dnr; + size_t n_dnr; char **domains; struct in6_addr *ntp; size_t ntp_count; diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c index a3e4e19e8e..0aa8469cc3 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c @@ -17,9 +17,10 @@ #include "fd-util.h" #include "socket-util.h" -int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) { +int dhcp6_network_bind_udp_socket(int ifindex, const struct in6_addr *local_address) { union sockaddr_union src = { .in6.sin6_family = AF_INET6, + .in6.sin6_addr = *ASSERT_PTR(local_address), .in6.sin6_port = htobe16(DHCP6_PORT_CLIENT), .in6.sin6_scope_id = ifindex, }; @@ -27,9 +28,6 @@ int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) { int r; assert(ifindex > 0); - assert(local_address); - - src.in6.sin6_addr = *local_address; s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_UDP); if (s < 0) @@ -58,20 +56,14 @@ int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) { return TAKE_FD(s); } -int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, - const void *packet, size_t len) { +int dhcp6_network_send_udp_socket(int s, const struct in6_addr *server_address, const void *packet, size_t len) { union sockaddr_union dest = { .in6.sin6_family = AF_INET6, + .in6.sin6_addr = *ASSERT_PTR(server_address), .in6.sin6_port = htobe16(DHCP6_PORT_SERVER), }; - int r; - assert(server_address); - - memcpy(&dest.in6.sin6_addr, server_address, sizeof(dest.in6.sin6_addr)); - - r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6)); - if (r < 0) + if (sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6)) < 0) return -errno; return 0; diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c index 83f40f3f02..507cd3547d 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c @@ -206,6 +206,7 @@ bool dhcp6_option_can_request(uint16_t option) { case SD_DHCP6_OPTION_V6_DOTS_RI: case SD_DHCP6_OPTION_V6_DOTS_ADDRESS: case SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF: + case SD_DHCP6_OPTION_V6_DNR: return true; default: return false; @@ -820,74 +821,6 @@ int dhcp6_option_parse_addresses( return 0; } -static int parse_domain(const uint8_t **data, size_t *len, char **ret) { - _cleanup_free_ char *domain = NULL; - const uint8_t *optval; - size_t optlen, n = 0; - int r; - - assert(data); - assert(len); - assert(*data || *len == 0); - assert(ret); - - optval = *data; - optlen = *len; - - if (optlen <= 1) - return -ENODATA; - - for (;;) { - const char *label; - uint8_t c; - - if (optlen == 0) - break; - - c = *optval; - optval++; - optlen--; - - if (c == 0) - /* End label */ - break; - if (c > 63) - return -EBADMSG; - if (c > optlen) - return -EMSGSIZE; - - /* Literal label */ - label = (const char*) optval; - optval += c; - optlen -= c; - - if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; - - if (n != 0) - domain[n++] = '.'; - - r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - n += r; - } - - if (n > 0) { - if (!GREEDY_REALLOC(domain, n + 1)) - return -ENOMEM; - - domain[n] = '\0'; - } - - *ret = TAKE_PTR(domain); - *data = optval; - *len = optlen; - - return n; -} - int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret) { _cleanup_free_ char *domain = NULL; int r; @@ -895,7 +828,7 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **r assert(optval || optlen == 0); assert(ret); - r = parse_domain(&optval, &optlen, &domain); + r = dns_name_from_wire_format(&optval, &optlen, &domain); if (r < 0) return r; if (r == 0) @@ -922,11 +855,11 @@ int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, cha while (optlen > 0) { _cleanup_free_ char *name = NULL; - r = parse_domain(&optval, &optlen, &name); + r = dns_name_from_wire_format(&optval, &optlen, &name); if (r < 0) return r; - if (r == 0) - continue; + if (dns_name_is_root(name)) /* root domain */ + return -EBADMSG; r = strv_consume(&names, TAKE_PTR(name)); if (r < 0) diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h index c70f93203d..39f5040f9d 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h @@ -28,9 +28,11 @@ typedef struct DHCP6Message DHCP6Message; #define DHCP6_MIN_OPTIONS_SIZE \ 1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr) -#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \ - { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } } +#define IN6_ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS \ + ((const struct in6_addr) { { { \ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, \ + } } } ) enum { DHCP6_PORT_SERVER = 547, @@ -150,9 +152,9 @@ typedef enum DHCP6FQDNFlag { DHCP6_FQDN_FLAG_N = 1 << 2, } DHCP6FQDNFlag; -const char *dhcp6_state_to_string(DHCP6State s) _const_; -const char *dhcp6_message_type_to_string(DHCP6MessageType s) _const_; +const char* dhcp6_state_to_string(DHCP6State s) _const_; +const char* dhcp6_message_type_to_string(DHCP6MessageType s) _const_; DHCP6MessageType dhcp6_message_type_from_string(const char *s) _pure_; -const char *dhcp6_message_status_to_string(DHCP6Status s) _const_; +const char* dhcp6_message_status_to_string(DHCP6Status s) _const_; DHCP6Status dhcp6_message_status_from_string(const char *s) _pure_; int dhcp6_message_status_to_errno(DHCP6Status s); diff --git a/src/libnm-systemd-core/src/libsystemd-network/network-common.c b/src/libnm-systemd-core/src/libsystemd-network/network-common.c index b639e9ca5a..8a0abe8fe8 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/network-common.c +++ b/src/libnm-systemd-core/src/libsystemd-network/network-common.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "env-util.h" -#include "format-util.h" +#include "format-ifname.h" #include "network-common.h" #include "socket-util.h" #include "unaligned.h" diff --git a/src/libnm-systemd-core/src/libsystemd-network/network-internal.h b/src/libnm-systemd-core/src/libsystemd-network/network-internal.h deleted file mode 100644 index 5aa225e977..0000000000 --- a/src/libnm-systemd-core/src/libsystemd-network/network-internal.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include -#include - -#include "sd-dhcp-lease.h" - -size_t serialize_in_addrs(FILE *f, - const struct in_addr *addresses, - size_t size, - bool *with_leading_space, - bool (*predicate)(const struct in_addr *addr)); -int deserialize_in_addrs(struct in_addr **addresses, const char *string); -void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, - size_t size, - bool *with_leading_space); -int deserialize_in6_addrs(struct in6_addr **addresses, const char *string); - -/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */ -struct sd_dhcp_route; -struct sd_dhcp_lease; - -void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route **routes, size_t size); -int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, const char *string); - -/* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */ -int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size); - -int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file); -int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file); diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c index 5a26102e23..3e992d7cad 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c @@ -743,8 +743,6 @@ static int client_append_mudurl(sd_dhcp6_client *client, uint8_t **buf, size_t * int dhcp6_client_send_message(sd_dhcp6_client *client) { _cleanup_free_ uint8_t *buf = NULL; - struct in6_addr all_servers = - IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; struct sd_dhcp6_option *j; usec_t elapsed_usec, time_now; be16_t elapsed_time; @@ -839,7 +837,7 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) { if (r < 0) return r; - r = dhcp6_network_send_udp_socket(client->fd, &all_servers, buf, offset); + r = dhcp6_network_send_udp_socket(client->fd, &IN6_ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS, buf, offset); if (r < 0) return r; @@ -1330,7 +1328,7 @@ static int client_receive_message( return 0; } if ((size_t) len < sizeof(DHCP6Message)) { - log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring"); + log_dhcp6_client(client, "Too small to be DHCPv6 message: ignoring"); return 0; } @@ -1406,7 +1404,7 @@ int sd_dhcp6_client_stop(sd_dhcp6_client *client) { r = client_send_release(client); if (r < 0) log_dhcp6_client_errno(client, r, - "Failed to send DHCP6 release message, ignoring: %m"); + "Failed to send DHCPv6 release message, ignoring: %m"); client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP); @@ -1417,7 +1415,8 @@ int sd_dhcp6_client_stop(sd_dhcp6_client *client) { } int sd_dhcp6_client_is_running(sd_dhcp6_client *client) { - assert_return(client, -EINVAL); + if (!client) + return false; return client->state != DHCP6_STATE_STOPPED; } diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c index e5d6547588..2ff1e87a2e 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c @@ -8,7 +8,9 @@ #include "alloc-util.h" #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" +#include "dns-domain.h" #include "network-common.h" +#include "sort-util.h" #include "strv.h" #include "unaligned.h" @@ -424,7 +426,7 @@ int dhcp6_lease_add_domains(sd_dhcp6_lease *lease, const uint8_t *optval, size_t if (r < 0) return r; - return strv_extend_strv(&lease->domains, domains, true); + return strv_extend_strv_consume(&lease->domains, TAKE_PTR(domains), /* filter_duplicates = */ true); } int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) { @@ -438,6 +440,103 @@ int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) { return strv_length(lease->domains); } +static int dhcp6_lease_add_dnr(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { + int r; + + assert(lease); + + _cleanup_(sd_dns_resolver_done) sd_dns_resolver res = {}; + + size_t offset = 0; + + /* priority */ + if (optlen - offset < sizeof(uint16_t)) + return -EBADMSG; + res.priority = unaligned_read_be16(optval + offset); + offset += sizeof(uint16_t); + + /* adn */ + if (optlen - offset < sizeof(uint16_t)) + return -EBADMSG; + size_t ilen = unaligned_read_be16(optval + offset); + offset += sizeof(uint16_t); + if (offset + ilen > optlen) + return -EBADMSG; + + r = dhcp6_option_parse_domainname(optval + offset, ilen, &res.auth_name); + if (r < 0) + return r; + r = dns_name_is_valid_ldh(res.auth_name); + if (r < 0) + return r; + if (!r) + return -EBADMSG; + offset += ilen; + + /* RFC9463 § 3.1.6: adn only mode */ + if (offset == optlen) + return 0; + + /* addrs */ + if (optlen - offset < sizeof(uint16_t)) + return -EBADMSG; + ilen = unaligned_read_be16(optval + offset); + offset += sizeof(uint16_t); + if (offset + ilen > optlen) + return -EBADMSG; + + _cleanup_free_ struct in6_addr *addrs = NULL; + size_t n_addrs = 0; + + r = dhcp6_option_parse_addresses(optval + offset, ilen, &addrs, &n_addrs); + if (r < 0) + return r; + if (n_addrs == 0) + return -EBADMSG; + offset += ilen; + + res.addrs = new(union in_addr_union, n_addrs); + if (!res.addrs) + return -ENOMEM; + + for (size_t i = 0; i < n_addrs; i++) { + union in_addr_union addr = {.in6 = addrs[i]}; + /* RFC9463 § 6.2 client MUST discard multicast and host loopback addresses */ + if (in_addr_is_multicast(AF_INET6, &addr) || + in_addr_is_localhost(AF_INET6, &addr)) + return -EBADMSG; + res.addrs[i] = addr; + } + res.n_addrs = n_addrs; + res.family = AF_INET6; + + /* svc params */ + r = dnr_parse_svc_params(optval + offset, optlen-offset, &res); + if (r < 0) + return r; + + /* Append this resolver */ + if (!GREEDY_REALLOC(lease->dnr, lease->n_dnr+1)) + return -ENOMEM; + + lease->dnr[lease->n_dnr++] = TAKE_STRUCT(res); + + typesafe_qsort(lease->dnr, lease->n_dnr, dns_resolver_prio_compare); + + return 1; +} + +int sd_dhcp6_lease_get_dnr(sd_dhcp6_lease *lease, sd_dns_resolver **ret) { + assert_return(lease, -EINVAL); + assert_return(ret, -EINVAL); + + if (!lease->dnr) + return -ENODATA; + + *ret = lease->dnr; + return lease->n_dnr; +} + int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { int r; @@ -763,8 +862,7 @@ static int dhcp6_lease_parse_message( continue; } - dhcp6_ia_free(lease->ia_na); - lease->ia_na = TAKE_PTR(ia); + free_and_replace_full(lease->ia_na, ia, dhcp6_ia_free); break; } case SD_DHCP6_OPTION_IA_PD: { @@ -788,8 +886,7 @@ static int dhcp6_lease_parse_message( continue; } - dhcp6_ia_free(lease->ia_pd); - lease->ia_pd = TAKE_PTR(ia); + free_and_replace_full(lease->ia_pd, ia, dhcp6_ia_free); break; } case SD_DHCP6_OPTION_RAPID_COMMIT: @@ -851,6 +948,15 @@ static int dhcp6_lease_parse_message( irt = unaligned_be32_sec_to_usec(optval, /* max_as_infinity = */ false); break; + case SD_DHCP6_OPTION_V6_DNR: + r = dhcp6_lease_add_dnr(lease, optval, optlen); + if (r < 0) + return log_dhcp6_client_errno(client, r, "Failed to parse DNR option, ignoring: %m"); + if (r == 0) + log_dhcp6_client(client, "Received ADN-only DNRv6 option, ignoring."); + + break; + case SD_DHCP6_OPTION_VENDOR_OPTS: r = dhcp6_lease_add_vendor_option(lease, optval, optlen); if (r < 0) @@ -904,6 +1010,7 @@ static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) { dhcp6_ia_free(lease->ia_na); dhcp6_ia_free(lease->ia_pd); free(lease->dns); + dns_resolver_done_many(lease->dnr, lease->n_dnr); free(lease->fqdn); free(lease->captive_portal); strv_free(lease->domains); diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c index cd85ec9c62..1c148b8573 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.c @@ -711,7 +711,7 @@ static int device_tag(sd_device *device, const char *tag, bool add) { assert(device); assert(tag); - r = device_get_device_id(device, &id); + r = sd_device_get_device_id(device, &id); if (r < 0) return r; @@ -797,7 +797,7 @@ static int device_get_db_path(sd_device *device, char **ret) { assert(device); assert(ret); - r = device_get_device_id(device, &id); + r = sd_device_get_device_id(device, &id); if (r < 0) return r; diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h index e1f3b6e80c..eab54203f0 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-private.h @@ -20,10 +20,12 @@ int device_opendir(sd_device *device, const char *subdir, DIR **ret); int device_get_property_bool(sd_device *device, const char *key); int device_get_property_int(sd_device *device, const char *key, int *ret); int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value); -int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value); +int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, unsigned base, unsigned *ret_value); +static inline int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value) { + return device_get_sysattr_unsigned_full(device, sysattr, 0, ret_value); +} int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value); int device_get_sysattr_bool(sd_device *device, const char *sysattr); -int device_get_device_id(sd_device *device, const char **ret); int device_get_devlink_priority(sd_device *device, int *ret); int device_get_devnode_mode(sd_device *device, mode_t *ret); int device_get_devnode_uid(sd_device *device, uid_t *ret); @@ -73,5 +75,5 @@ int device_read_uevent_file(sd_device *device); int device_set_action(sd_device *device, sd_device_action_t a); sd_device_action_t device_action_from_string(const char *s) _pure_; -const char *device_action_to_string(sd_device_action_t a) _const_; +const char* device_action_to_string(sd_device_action_t a) _const_; void dump_device_action_table(void); diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h b/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h index 534a296715..b17993d554 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h @@ -100,7 +100,7 @@ static inline int devname_from_stat_rdev(const struct stat *st, char **ret) { assert(st); return devname_from_devnum(st->st_mode, st->st_rdev, ret); } -int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret); +int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret_devname); char** device_make_log_fields(sd_device *device); diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c b/src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c index bfc2a8a9ca..a608ae326f 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/sd-device.c @@ -214,7 +214,7 @@ int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { /* Only operate on sysfs, i.e. refuse going down into /sys/fs/cgroup/ or similar places where * things are not arranged as kobjects in kernel, and hence don't necessarily have * kobject/attribute structure. */ - r = getenv_bool_secure("SYSTEMD_DEVICE_VERIFY_SYSFS"); + r = secure_getenv_bool("SYSTEMD_DEVICE_VERIFY_SYSFS"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_DEVICE_VERIFY_SYSFS value: %m"); if (r != 0) { @@ -234,7 +234,7 @@ int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { r = path_simplify_alloc(_syspath, &syspath); if (r < 0) - return r; + return log_oom_debug(); } assert_se(devpath = startswith(syspath, "/sys")); @@ -345,9 +345,11 @@ _public_ int sd_device_new_from_ifname(sd_device **ret, const char *ifname) { assert_return(ret, -EINVAL); assert_return(ifname, -EINVAL); - r = device_new_from_main_ifname(ret, ifname); - if (r >= 0) - return r; + if (ifname_valid(ifname)) { + r = device_new_from_main_ifname(ret, ifname); + if (r >= 0) + return r; + } r = rtnl_resolve_ifname_full(NULL, RESOLVE_IFNAME_ALTERNATIVE | RESOLVE_IFNAME_NUMERIC, ifname, &main_name, NULL); if (r < 0) @@ -384,25 +386,79 @@ _public_ int sd_device_new_from_ifindex(sd_device **ret, int ifindex) { return 0; } -static int device_strjoin_new( +static int device_new_from_path_join( + sd_device **device, + const char *subsystem, + const char *driver_subsystem, + const char *sysname, const char *a, const char *b, const char *c, - const char *d, - sd_device **ret) { + const char *d) { - const char *p; + _cleanup_(sd_device_unrefp) sd_device *new_device = NULL; + _cleanup_free_ char *p = NULL; int r; - p = strjoina(a, b, c, d); - if (access(p, F_OK) < 0) - return IN_SET(errno, ENOENT, ENAMETOOLONG) ? 0 : -errno; /* If this sysfs is too long then it doesn't exist either */ + assert(device); + assert(sysname); - r = sd_device_new_from_syspath(ret, p); + p = path_join(a, b, c, d); + if (!p) + return -ENOMEM; + + r = sd_device_new_from_syspath(&new_device, p); + if (r == -ENODEV) + return 0; if (r < 0) return r; - return 1; + /* Check if the found device really has the expected subsystem and sysname, for safety. */ + if (!device_in_subsystem(new_device, subsystem)) + return 0; + + const char *new_driver_subsystem = NULL; + (void) sd_device_get_driver_subsystem(new_device, &new_driver_subsystem); + + if (!streq_ptr(driver_subsystem, new_driver_subsystem)) + return 0; + + const char *new_sysname; + r = sd_device_get_sysname(new_device, &new_sysname); + if (r < 0) + return r; + + if (!streq(sysname, new_sysname)) + return 0; + + /* If this is the first device we found, then take it. */ + if (!*device) { + *device = TAKE_PTR(new_device); + return 1; + } + + /* Unfortunately, (subsystem, sysname) pair is not unique. For examples, + * - /sys/bus/gpio and /sys/class/gpio, both have gpiochip%N. However, these point to different devpaths. + * - /sys/bus/mdio_bus and /sys/class/mdio_bus, + * - /sys/bus/mei and /sys/class/mei, + * - /sys/bus/typec and /sys/class/typec, and so on. + * Hence, if we already know a device, then we need to check if it is equivalent to the newly found one. */ + + const char *devpath, *new_devpath; + r = sd_device_get_devpath(*device, &devpath); + if (r < 0) + return r; + + r = sd_device_get_devpath(new_device, &new_devpath); + if (r < 0) + return r; + + if (!streq(devpath, new_devpath)) + return log_debug_errno(SYNTHETIC_ERRNO(ETOOMANYREFS), + "sd-device: found multiple devices for subsystem=%s and sysname=%s, refusing: %s, %s", + subsystem, sysname, devpath, new_devpath); + + return 1; /* Fortunately, they are consistent. */ } _public_ int sd_device_new_from_subsystem_sysname( @@ -410,6 +466,7 @@ _public_ int sd_device_new_from_subsystem_sysname( const char *subsystem, const char *sysname) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; char *name; int r; @@ -428,19 +485,15 @@ _public_ int sd_device_new_from_subsystem_sysname( if (streq(subsystem, "subsystem")) { FOREACH_STRING(s, "/sys/bus/", "/sys/class/") { - r = device_strjoin_new(s, name, NULL, NULL, ret); + r = device_new_from_path_join(&device, subsystem, /* driver_subsystem = */ NULL, sysname, s, name, NULL, NULL); if (r < 0) return r; - if (r > 0) - return 0; } } else if (streq(subsystem, "module")) { - r = device_strjoin_new("/sys/module/", name, NULL, NULL, ret); + r = device_new_from_path_join(&device, subsystem, /* driver_subsystem = */ NULL, sysname, "/sys/module/", name, NULL, NULL); if (r < 0) return r; - if (r > 0) - return 0; } else if (streq(subsystem, "drivers")) { const char *sep; @@ -452,35 +505,33 @@ _public_ int sd_device_new_from_subsystem_sysname( sep++; if (streq(sep, "drivers")) /* If the sysname is "drivers", then it's the drivers directory itself that is meant. */ - r = device_strjoin_new("/sys/bus/", subsys, "/drivers", NULL, ret); + r = device_new_from_path_join(&device, subsystem, subsys, "drivers", "/sys/bus/", subsys, "/drivers", NULL); else - r = device_strjoin_new("/sys/bus/", subsys, "/drivers/", sep, ret); + r = device_new_from_path_join(&device, subsystem, subsys, sep, "/sys/bus/", subsys, "/drivers/", sep); if (r < 0) return r; - if (r > 0) - return 0; } } - r = device_strjoin_new("/sys/bus/", subsystem, "/devices/", name, ret); + r = device_new_from_path_join(&device, subsystem, /* driver_subsystem = */ NULL, sysname, "/sys/bus/", subsystem, "/devices/", name); if (r < 0) return r; - if (r > 0) - return 0; - r = device_strjoin_new("/sys/class/", subsystem, "/", name, ret); + r = device_new_from_path_join(&device, subsystem, /* driver_subsystem = */ NULL, sysname, "/sys/class/", subsystem, name, NULL); if (r < 0) return r; - if (r > 0) - return 0; - r = device_strjoin_new("/sys/firmware/", subsystem, "/", name, ret); + /* Note that devices under /sys/firmware/ (e.g. /sys/firmware/devicetree/base/) do not have + * subsystem. Hence, pass NULL for subsystem. See issue #35861. */ + r = device_new_from_path_join(&device, /* subsystem = */ NULL, /* driver_subsystem = */ NULL, sysname, "/sys/firmware/", subsystem, name, NULL); if (r < 0) return r; - if (r > 0) - return 0; - return -ENODEV; + if (!device) + return -ENODEV; + + *ret = TAKE_PTR(device); + return 0; } _public_ int sd_device_new_from_stat_rdev(sd_device **ret, const struct stat *st) { @@ -490,10 +541,8 @@ _public_ int sd_device_new_from_stat_rdev(sd_device **ret, const struct stat *st return device_new_from_mode_and_devnum(ret, st->st_mode, st->st_rdev); } -_public_ int sd_device_new_from_devname(sd_device **ret, const char *devname) { - struct stat st; - dev_t devnum; - mode_t mode; +static int device_new_from_devname(sd_device **ret, const char *devname, bool strict) { + int r; assert_return(ret, -EINVAL); assert_return(devname, -EINVAL); @@ -501,28 +550,41 @@ _public_ int sd_device_new_from_devname(sd_device **ret, const char *devname) { /* This function actually accepts both devlinks and devnames, i.e. both symlinks and device * nodes below /dev/. */ - /* Also ignore when the specified path is "/dev". */ - if (isempty(path_startswith(devname, "/dev"))) + if (strict && isempty(path_startswith(devname, "/dev/"))) return -EINVAL; + dev_t devnum; + mode_t mode; if (device_path_parse_major_minor(devname, &mode, &devnum) >= 0) /* Let's shortcut when "/dev/block/maj:min" or "/dev/char/maj:min" is specified. * In that case, we can directly convert the path to syspath, hence it is not necessary * that the specified path exists. So, this works fine without udevd being running. */ return device_new_from_mode_and_devnum(ret, mode, devnum); - if (stat(devname, &st) < 0) - return ERRNO_IS_DEVICE_ABSENT(errno) ? -ENODEV : -errno; + _cleanup_free_ char *resolved = NULL; + struct stat st; + r = chase_and_stat(devname, /* root = */ NULL, /* flags = */ 0, &resolved, &st); + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) + return -ENODEV; + if (r < 0) + return r; + + if (isempty(path_startswith(resolved, "/dev/"))) + return -EINVAL; return sd_device_new_from_stat_rdev(ret, &st); } +_public_ int sd_device_new_from_devname(sd_device **ret, const char *devname) { + return device_new_from_devname(ret, devname, /* strict = */ true); +} + _public_ int sd_device_new_from_path(sd_device **ret, const char *path) { assert_return(ret, -EINVAL); assert_return(path, -EINVAL); - if (path_startswith(path, "/dev")) - return sd_device_new_from_devname(ret, path); + if (device_new_from_devname(ret, path, /* strict = */ false) >= 0) + return 0; return device_new_from_syspath(ret, path, /* strict = */ false); } @@ -1196,6 +1258,20 @@ _public_ int sd_device_get_subsystem(sd_device *device, const char **ret) { return 0; } +_public_ int sd_device_get_driver_subsystem(sd_device *device, const char **ret) { + assert_return(device, -EINVAL); + + if (!device_in_subsystem(device, "drivers")) + return -ENOENT; + + assert(device->driver_subsystem); + + if (ret) + *ret = device->driver_subsystem; + + return 0; +} + _public_ int sd_device_get_devtype(sd_device *device, const char **devtype) { int r; @@ -1211,7 +1287,7 @@ _public_ int sd_device_get_devtype(sd_device *device, const char **devtype) { if (devtype) *devtype = device->devtype; - return !!device->devtype; + return 0; } _public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *device, const char *subsystem, const char *devtype, sd_device **ret) { @@ -1621,9 +1697,8 @@ static int handle_db_line(sd_device *device, char key, const char *value) { } } -int device_get_device_id(sd_device *device, const char **ret) { - assert(device); - assert(ret); +_public_ int sd_device_get_device_id(sd_device *device, const char **ret) { + assert_return(device, -EINVAL); if (!device->device_id) { _cleanup_free_ char *id = NULL; @@ -1673,7 +1748,8 @@ int device_get_device_id(sd_device *device, const char **ret) { device->device_id = TAKE_PTR(id); } - *ret = device->device_id; + if (ret) + *ret = device->device_id; return 0; } @@ -2399,7 +2475,7 @@ int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_valu return v > 0; } -int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value) { +int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, unsigned base, unsigned *ret_value) { const char *value; int r; @@ -2408,7 +2484,7 @@ int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned return r; unsigned v; - r = safe_atou(value, &v); + r = safe_atou_full(value, base, &v); if (r < 0) return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h b/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h index f4e38d78d0..d05bcf0538 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h +++ b/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h @@ -189,6 +189,9 @@ struct inode_data { * iteration. */ int fd; + /* The path that the fd points to. The field is optional. */ + char *path; + /* The inotify "watch descriptor" */ int wd; diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c index 2338df1d62..862455e19c 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c +++ b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c @@ -168,3 +168,12 @@ int event_add_child_pidref( return sd_event_add_child(e, s, pid->pid, options, callback, userdata); } + +dual_timestamp* event_dual_timestamp_now(sd_event *e, dual_timestamp *ts) { + assert(e); + assert(ts); + + assert_se(sd_event_now(e, CLOCK_REALTIME, &ts->realtime) >= 0); + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &ts->monotonic) >= 0); + return ts; +} diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h index 6259d5ae25..7002ca37da 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h +++ b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h @@ -36,3 +36,5 @@ static inline int event_source_disable(sd_event_source *s) { int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handler_t callback, void *userdata); int event_add_child_pidref(sd_event *e, sd_event_source **s, const PidRef *pid, int options, sd_event_child_handler_t callback, void *userdata); + +dual_timestamp* event_dual_timestamp_now(sd_event *e, dual_timestamp *ts); diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c b/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c index 338609b186..3d1b6fb228 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c +++ b/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c @@ -25,9 +25,11 @@ #include "missing_magic.h" #include "missing_syscall.h" #include "missing_threads.h" +#include "missing_wait.h" #include "origin-id.h" #include "path-util.h" #include "prioq.h" +#include "pidfd-util.h" #include "process-util.h" #include "psi-util.h" #include "set.h" @@ -182,7 +184,7 @@ static thread_local sd_event *default_event = NULL; static void source_disconnect(sd_event_source *s); static void event_gc_inode_data(sd_event *e, struct inode_data *d); -static sd_event *event_resolve(sd_event *e) { +static sd_event* event_resolve(sd_event *e) { return e == SD_EVENT_DEFAULT ? default_event : e; } @@ -338,7 +340,7 @@ static void free_clock_data(struct clock_data *d) { prioq_free(d->latest); } -static sd_event *event_free(sd_event *e) { +static sd_event* event_free(sd_event *e) { sd_event_source *s; assert(e); @@ -443,7 +445,7 @@ fail: } /* Define manually so we can add the origin check */ -_public_ sd_event *sd_event_ref(sd_event *e) { +_public_ sd_event* sd_event_ref(sd_event *e) { if (!e) return NULL; if (event_origin_changed(e)) @@ -471,8 +473,13 @@ _public_ sd_event* sd_event_unref(sd_event *e) { _unused_ _cleanup_(sd_event_unrefp) sd_event *_ref = sd_event_ref(e); _public_ sd_event_source* sd_event_source_disable_unref(sd_event_source *s) { - if (s) - (void) sd_event_source_set_enabled(s, SD_EVENT_OFF); + int r; + + r = sd_event_source_set_enabled(s, SD_EVENT_OFF); + if (r < 0) + log_debug_errno(r, "Failed to disable event source %p (%s): %m", + s, strna(s->description)); + return sd_event_source_unref(s); } @@ -1068,6 +1075,8 @@ static void source_disconnect(sd_event_source *s) { } static sd_event_source* source_free(sd_event_source *s) { + int r; + assert(s); source_disconnect(s); @@ -1081,31 +1090,23 @@ static sd_event_source* source_free(sd_event_source *s) { if (s->child.process_owned) { if (!s->child.exited) { - bool sent = false; - - if (s->child.pidfd >= 0) { - if (pidfd_send_signal(s->child.pidfd, SIGKILL, NULL, 0) < 0) { - if (errno == ESRCH) /* Already dead */ - sent = true; - else if (!ERRNO_IS_NOT_SUPPORTED(errno)) - log_debug_errno(errno, "Failed to kill process " PID_FMT " via pidfd_send_signal(), re-trying via kill(): %m", - s->child.pid); - } else - sent = true; - } - - if (!sent) - if (kill(s->child.pid, SIGKILL) < 0) - if (errno != ESRCH) /* Already dead */ - log_debug_errno(errno, "Failed to kill process " PID_FMT " via kill(), ignoring: %m", - s->child.pid); + if (s->child.pidfd >= 0) + r = RET_NERRNO(pidfd_send_signal(s->child.pidfd, SIGKILL, NULL, 0)); + else + r = RET_NERRNO(kill(s->child.pid, SIGKILL)); + if (r < 0 && r != -ESRCH) + log_debug_errno(r, "Failed to kill process " PID_FMT ", ignoring: %m", + s->child.pid); } if (!s->child.waited) { siginfo_t si = {}; /* Reap the child if we can */ - (void) waitid(P_PID, s->child.pid, &si, WEXITED); + if (s->child.pidfd >= 0) + (void) waitid(P_PIDFD, s->child.pidfd, &si, WEXITED); + else + (void) waitid(P_PID, s->child.pid, &si, WEXITED); } } @@ -1175,7 +1176,7 @@ static int source_set_pending(sd_event_source *s, bool b) { return 1; } -static sd_event_source *source_new(sd_event *e, bool floating, EventSourceType type) { +static sd_event_source* source_new(sd_event *e, bool floating, EventSourceType type) { /* Let's allocate exactly what we need. Note that the difference of the smallest event source * structure to the largest is 144 bytes on x86-64 at the time of writing, i.e. more than two cache @@ -1572,11 +1573,6 @@ static int child_exit_callback(sd_event_source *s, const siginfo_t *si, void *us return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); } -static bool shall_use_pidfd(void) { - /* Mostly relevant for debugging, i.e. this is used in test-event.c to test the event loop once with and once without pidfd */ - return getenv_bool_secure("SYSTEMD_PIDFD") != 0; -} - _public_ int sd_event_add_child( sd_event *e, sd_event_source **ret, @@ -1624,34 +1620,29 @@ _public_ int sd_event_add_child( if (!s) return -ENOMEM; + /* We always take a pidfd here if we can, even if we wait for anything else than WEXITED, so that we + * pin the PID, and make regular waitid() handling race-free. */ + + s->child.pidfd = pidfd_open(pid, 0); + if (s->child.pidfd < 0) + return -errno; + + s->child.pidfd_owned = true; /* If we allocate the pidfd we own it by default */ + s->wakeup = WAKEUP_EVENT_SOURCE; s->child.options = options; s->child.callback = callback; s->userdata = userdata; s->enabled = SD_EVENT_ONESHOT; - /* We always take a pidfd here if we can, even if we wait for anything else than WEXITED, so that we - * pin the PID, and make regular waitid() handling race-free. */ - - if (shall_use_pidfd()) { - s->child.pidfd = pidfd_open(pid, 0); - if (s->child.pidfd < 0) { - /* Propagate errors unless the syscall is not supported or blocked */ - if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) - return -errno; - } else - s->child.pidfd_owned = true; /* If we allocate the pidfd we own it by default */ - } else - s->child.pidfd = -EBADF; - if (EVENT_SOURCE_WATCH_PIDFD(s)) { - /* We have a pidfd and we only want to watch for exit */ + /* We only want to watch for exit */ r = source_child_pidfd_register(s, s->enabled); if (r < 0) return r; } else { - /* We have no pidfd or we shall wait for some other event than WEXITED */ + /* We shall wait for some other event than WEXITED */ r = event_make_signal_data(e, SIGCHLD, NULL); if (r < 0) return r; @@ -1681,7 +1672,6 @@ _public_ int sd_event_add_child_pidfd( sd_event_child_handler_t callback, void *userdata) { - _cleanup_(source_freep) sd_event_source *s = NULL; pid_t pid; int r; @@ -1722,17 +1712,12 @@ _public_ int sd_event_add_child_pidfd( s->wakeup = WAKEUP_EVENT_SOURCE; s->child.pidfd = pidfd; - s->child.pid = pid; s->child.options = options; s->child.callback = callback; s->child.pidfd_owned = false; /* If we got the pidfd passed in we don't own it by default (similar to the IO fd case) */ s->userdata = userdata; s->enabled = SD_EVENT_ONESHOT; - r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s); - if (r < 0) - return r; - if (EVENT_SOURCE_WATCH_PIDFD(s)) { /* We only want to watch for WEXITED */ r = source_child_pidfd_register(s, s->enabled); @@ -1747,6 +1732,11 @@ _public_ int sd_event_add_child_pidfd( e->need_process_child = true; } + r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s); + if (r < 0) + return r; + + s->child.pid = pid; e->n_online_child_sources++; if (ret) @@ -2272,6 +2262,7 @@ static void event_free_inode_data( assert_se(hashmap_remove(d->inotify_data->inodes, d) == d); } + free(d->path); free(d); } @@ -2415,7 +2406,7 @@ static int inode_data_realize_watch(sd_event *e, struct inode_data *d) { wd = inotify_add_watch_fd(d->inotify_data->fd, d->fd, combined_mask); if (wd < 0) - return -errno; + return wd; if (d->wd < 0) { r = hashmap_put(d->inotify_data->wd, INT_TO_PTR(wd), d); @@ -2512,6 +2503,15 @@ static int event_add_inotify_fd_internal( } LIST_PREPEND(to_close, e->inode_data_to_close_list, inode_data); + + _cleanup_free_ char *path = NULL; + r = fd_get_path(inode_data->fd, &path); + if (r < 0 && r != -ENOSYS) { /* The path is optional, hence ignore -ENOSYS. */ + event_gc_inode_data(e, inode_data); + return r; + } + + free_and_replace(inode_data->path, path); } /* Link our event source to the inode data object */ @@ -2601,18 +2601,18 @@ _public_ int sd_event_source_set_description(sd_event_source *s, const char *des return free_and_strdup(&s->description, description); } -_public_ int sd_event_source_get_description(sd_event_source *s, const char **description) { +_public_ int sd_event_source_get_description(sd_event_source *s, const char **ret) { assert_return(s, -EINVAL); - assert_return(description, -EINVAL); + assert_return(ret, -EINVAL); if (!s->description) return -ENXIO; - *description = s->description; + *ret = s->description; return 0; } -_public_ sd_event *sd_event_source_get_event(sd_event_source *s) { +_public_ sd_event* sd_event_source_get_event(sd_event_source *s) { assert_return(s, NULL); assert_return(!event_origin_changed(s->event), NULL); @@ -2637,7 +2637,7 @@ _public_ int sd_event_source_get_io_fd(sd_event_source *s) { } _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { - int r; + int saved_fd, r; assert_return(s, -EINVAL); assert_return(fd >= 0, -EBADF); @@ -2647,16 +2647,12 @@ _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { if (s->io.fd == fd) return 0; - if (event_source_is_offline(s)) { - s->io.fd = fd; - s->io.registered = false; - } else { - int saved_fd; + saved_fd = s->io.fd; + s->io.fd = fd; - saved_fd = s->io.fd; - assert(s->io.registered); + assert(event_source_is_offline(s) == !s->io.registered); - s->io.fd = fd; + if (s->io.registered) { s->io.registered = false; r = source_io_register(s, s->enabled, s->io.events); @@ -2669,6 +2665,9 @@ _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { (void) epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL); } + if (s->io.owned) + safe_close(saved_fd); + return 0; } @@ -2689,13 +2688,13 @@ _public_ int sd_event_source_set_io_fd_own(sd_event_source *s, int own) { return 0; } -_public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events) { +_public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t *ret) { assert_return(s, -EINVAL); - assert_return(events, -EINVAL); + assert_return(ret, -EINVAL); assert_return(s->type == SOURCE_IO, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - *events = s->io.events; + *ret = s->io.events; return 0; } @@ -2727,14 +2726,14 @@ _public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events) return 0; } -_public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents) { +_public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t *ret) { assert_return(s, -EINVAL); - assert_return(revents, -EINVAL); + assert_return(ret, -EINVAL); assert_return(s->type == SOURCE_IO, -EDOM); assert_return(s->pending, -ENODATA); assert_return(!event_origin_changed(s->event), -ECHILD); - *revents = s->io.revents; + *ret = s->io.revents; return 0; } @@ -2746,11 +2745,12 @@ _public_ int sd_event_source_get_signal(sd_event_source *s) { return s->signal.sig; } -_public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority) { +_public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *ret) { assert_return(s, -EINVAL); + assert_return(ret, -EINVAL); assert_return(!event_origin_changed(s->event), -ECHILD); - *priority = s->priority; + *ret = s->priority; return 0; } @@ -2798,6 +2798,13 @@ _public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority) } LIST_PREPEND(to_close, s->event->inode_data_to_close_list, new_inode_data); + + _cleanup_free_ char *path = NULL; + r = fd_get_path(new_inode_data->fd, &path); + if (r < 0 && r != -ENOSYS) + goto fail; + + free_and_replace(new_inode_data->path, path); } /* Move the event source to the new inode data structure */ @@ -3084,13 +3091,13 @@ _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) { return 0; } -_public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) { +_public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *ret) { assert_return(s, -EINVAL); - assert_return(usec, -EINVAL); + assert_return(ret, -EINVAL); assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - *usec = s->time.next; + *ret = s->time.next; return 0; } @@ -3134,13 +3141,13 @@ _public_ int sd_event_source_set_time_relative(sd_event_source *s, uint64_t usec return sd_event_source_set_time(s, usec); } -_public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec) { +_public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *ret) { assert_return(s, -EINVAL); - assert_return(usec, -EINVAL); + assert_return(ret, -EINVAL); assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - *usec = s->time.accuracy; + *ret = s->time.accuracy; return 0; } @@ -3166,23 +3173,23 @@ _public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec return 0; } -_public_ int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock) { +_public_ int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *ret) { assert_return(s, -EINVAL); - assert_return(clock, -EINVAL); + assert_return(ret, -EINVAL); assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - *clock = event_source_type_to_clock(s->type); + *ret = event_source_type_to_clock(s->type); return 0; } -_public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) { +_public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *ret) { assert_return(s, -EINVAL); - assert_return(pid, -EINVAL); + assert_return(ret, -EINVAL); assert_return(s->type == SOURCE_CHILD, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - *pid = s->child.pid; + *ret = s->child.pid; return 0; } @@ -3217,12 +3224,10 @@ _public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, cons if (si) copy = *si; - if (pidfd_send_signal(s->child.pidfd, sig, si ? © : NULL, 0) < 0) { - /* Let's propagate the error only if the system call is not implemented or prohibited */ - if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) - return -errno; - } else - return 0; + if (pidfd_send_signal(s->child.pidfd, sig, si ? © : NULL, 0) < 0) + return -errno; + + return 0; } /* Flags are only supported for pidfd_send_signal(), not for rt_sigqueueinfo(), hence let's refuse @@ -3282,13 +3287,29 @@ _public_ int sd_event_source_set_child_process_own(sd_event_source *s, int own) return 0; } -_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *mask) { +_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret) { assert_return(s, -EINVAL); - assert_return(mask, -EINVAL); + assert_return(ret, -EINVAL); assert_return(s->type == SOURCE_INOTIFY, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - *mask = s->inotify.mask; + *ret = s->inotify.mask; + return 0; +} + +_public_ int sd_event_source_get_inotify_path(sd_event_source *s, const char **ret) { + assert_return(s, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(s->type == SOURCE_INOTIFY, -EDOM); + assert_return(!event_origin_changed(s->event), -ECHILD); + + if (!s->inotify.inode_data) + return -ESTALE; /* already disconnected. */ + + if (!s->inotify.inode_data->path) + return -ENOSYS; /* /proc was not mounted? */ + + *ret = s->inotify.inode_data->path; return 0; } @@ -3831,7 +3852,8 @@ static int process_signal(sd_event *e, struct signal_data *d, uint32_t events, i if (_unlikely_(n != sizeof(si))) return -EIO; - assert(SIGNAL_VALID(si.ssi_signo)); + if (_unlikely_(!SIGNAL_VALID(si.ssi_signo))) + return -EIO; if (e->signal_sources) s = e->signal_sources[si.ssi_signo]; @@ -4532,7 +4554,7 @@ static int epoll_wait_usec( /* epoll_pwait2() was added to Linux 5.11 (2021-02-14) and to glibc in 2.35 (2022-02-03). In contrast * to other syscalls we don't bother with our own fallback syscall wrappers on old libcs, since this * is not that obvious to implement given the libc and kernel definitions differ in the last - * argument. Moreover, the only reason to use it is the more accurate time-outs (which is not a + * argument. Moreover, the only reason to use it is the more accurate timeouts (which is not a * biggie), let's hence rely on glibc's definitions, and fallback to epoll_pwait() when that's * missing. */ @@ -4817,13 +4839,13 @@ _public_ int sd_event_dispatch(sd_event *e) { static void event_log_delays(sd_event *e) { char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1], *p; - size_t l, i; + size_t l; p = b; l = sizeof(b); - for (i = 0; i < ELEMENTSOF(e->delays); i++) { - l = strpcpyf(&p, l, "%u ", e->delays[i]); - e->delays[i] = 0; + FOREACH_ELEMENT(delay, e->delays) { + l = strpcpyf(&p, l, "%u ", *delay); + *delay = 0; } log_debug("Event loop iterations: %s", b); } @@ -4884,7 +4906,6 @@ _public_ int sd_event_loop(sd_event *e) { assert_return(!event_origin_changed(e), -ECHILD); assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); - PROTECT_EVENT(e); while (e->state != SD_EVENT_FINISHED) { @@ -4912,16 +4933,16 @@ _public_ int sd_event_get_state(sd_event *e) { return e->state; } -_public_ int sd_event_get_exit_code(sd_event *e, int *code) { +_public_ int sd_event_get_exit_code(sd_event *e, int *ret) { assert_return(e, -EINVAL); assert_return(e = event_resolve(e), -ENOPKG); - assert_return(code, -EINVAL); assert_return(!event_origin_changed(e), -ECHILD); if (!e->exit_requested) return -ENODATA; - *code = e->exit_code; + if (ret) + *ret = e->exit_code; return 0; } @@ -4937,10 +4958,10 @@ _public_ int sd_event_exit(sd_event *e, int code) { return 0; } -_public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) { +_public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *ret) { assert_return(e, -EINVAL); assert_return(e = event_resolve(e), -ENOPKG); - assert_return(usec, -EINVAL); + assert_return(ret, -EINVAL); assert_return(!event_origin_changed(e), -ECHILD); if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock)) @@ -4948,11 +4969,11 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) { if (!triple_timestamp_is_set(&e->timestamp)) { /* Implicitly fall back to now() if we never ran before and thus have no cached time. */ - *usec = now(clock); + *ret = now(clock); return 1; } - *usec = triple_timestamp_by_clock(&e->timestamp, clock); + *ret = triple_timestamp_by_clock(&e->timestamp, clock); return 0; } @@ -4980,18 +5001,17 @@ _public_ int sd_event_default(sd_event **ret) { return 1; } -_public_ int sd_event_get_tid(sd_event *e, pid_t *tid) { +_public_ int sd_event_get_tid(sd_event *e, pid_t *ret) { assert_return(e, -EINVAL); assert_return(e = event_resolve(e), -ENOPKG); - assert_return(tid, -EINVAL); + assert_return(ret, -EINVAL); assert_return(!event_origin_changed(e), -ECHILD); - if (e->tid != 0) { - *tid = e->tid; - return 0; - } + if (e->tid == 0) + return -ENXIO; - return -ENXIO; + *ret = e->tid; + return 0; } _public_ int sd_event_set_watchdog(sd_event *e, int b) { @@ -5219,6 +5239,9 @@ _public_ int sd_event_set_signal_exit(sd_event *e, int b) { int r; assert_return(e, -EINVAL); + assert_return(e = event_resolve(e), -ENOPKG); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_origin_changed(e), -ECHILD); if (b) { /* We want to maintain pointers to these event sources, so that we can destroy them when told @@ -5230,7 +5253,7 @@ _public_ int sd_event_set_signal_exit(sd_event *e, int b) { if (r < 0) return r; - assert(sd_event_source_set_floating(e->sigint_event_source, true) >= 0); + assert_se(sd_event_source_set_floating(e->sigint_event_source, true) >= 0); change = true; } @@ -5238,26 +5261,26 @@ _public_ int sd_event_set_signal_exit(sd_event *e, int b) { r = sd_event_add_signal(e, &e->sigterm_event_source, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, NULL, NULL); if (r < 0) { if (change) { - assert(sd_event_source_set_floating(e->sigint_event_source, false) >= 0); + assert_se(sd_event_source_set_floating(e->sigint_event_source, false) >= 0); e->sigint_event_source = sd_event_source_unref(e->sigint_event_source); } return r; } - assert(sd_event_source_set_floating(e->sigterm_event_source, true) >= 0); + assert_se(sd_event_source_set_floating(e->sigterm_event_source, true) >= 0); change = true; } } else { if (e->sigint_event_source) { - assert(sd_event_source_set_floating(e->sigint_event_source, false) >= 0); + assert_se(sd_event_source_set_floating(e->sigint_event_source, false) >= 0); e->sigint_event_source = sd_event_source_unref(e->sigint_event_source); change = true; } if (e->sigterm_event_source) { - assert(sd_event_source_set_floating(e->sigterm_event_source, false) >= 0); + assert_se(sd_event_source_set_floating(e->sigterm_event_source, false) >= 0); e->sigterm_event_source = sd_event_source_unref(e->sigterm_event_source); change = true; } diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c index 7e5cb95d30..298d21ed29 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c +++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c @@ -9,6 +9,8 @@ #include "hexdecoct.h" #include "id128-util.h" #include "io-util.h" +#include "namespace-util.h" +#include "process-util.h" #include "sha256.h" #include "stdio-util.h" #include "string-util.h" @@ -139,7 +141,7 @@ int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret) { assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(path); - fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0); + fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) return fd; @@ -185,7 +187,7 @@ int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id) { assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(path); - fd = xopenat(dir_fd, path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, /* xopen_flags = */ 0, 0444); + fd = xopenat_full(dir_fd, path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, /* xopen_flags = */ 0, 0444); if (fd < 0) return fd; @@ -268,3 +270,64 @@ sd_id128_t id128_digest(const void *data, size_t size) { return id128_make_v4_uuid(id); } + +int id128_get_boot_for_machine(const char *machine, sd_id128_t *ret) { + _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, rootfd = -EBADF; + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; + pid_t pid, child; + sd_id128_t id; + ssize_t k; + int r; + + assert(ret); + + if (isempty(machine)) + return sd_id128_get_boot(ret); + + r = container_get_leader(machine, &pid); + if (r < 0) + return r; + + r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, /* ret_userns_fd = */ NULL, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + r = namespace_fork("(sd-bootidns)", "(sd-bootid)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, + pidnsfd, mntnsfd, -1, -1, rootfd, &child); + if (r < 0) + return r; + if (r == 0) { + pair[0] = safe_close(pair[0]); + + r = id128_get_boot(&id); + if (r < 0) + _exit(EXIT_FAILURE); + + k = send(pair[1], &id, sizeof(id), MSG_NOSIGNAL); + if (k != sizeof(id)) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate_and_check("(sd-bootidns)", child, 0); + if (r < 0) + return r; + if (r != EXIT_SUCCESS) + return -EIO; + + k = recv(pair[0], &id, sizeof(id), 0); + if (k != sizeof(id)) + return -EIO; + + if (sd_id128_is_null(id)) + return -EIO; + + *ret = id; + return 0; +} diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h index 53ba50a8ac..458d430771 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h +++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h @@ -49,6 +49,9 @@ int id128_get_product(sd_id128_t *ret); sd_id128_t id128_digest(const void *data, size_t size); +int id128_get_boot(sd_id128_t *ret); +int id128_get_boot_for_machine(const char *machine, sd_id128_t *ret); + /* A helper to check for the three relevant cases of "machine ID not initialized" */ #define ERRNO_IS_NEG_MACHINE_ID_UNSET(r) \ IN_SET(r, \ diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c b/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c index 9fda79ae26..fc1107b4e8 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c @@ -13,6 +13,7 @@ #include "hmac.h" #include "id128-util.h" #include "io-util.h" +#include "keyring-util.h" #include "macro.h" #include "missing_syscall.h" #include "missing_threads.h" @@ -170,14 +171,24 @@ int id128_get_machine(const char *root, sd_id128_t *ret) { return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret); } +int id128_get_boot(sd_id128_t *ret) { + int r; + + assert(ret); + + r = id128_read("/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID | ID128_REFUSE_NULL, ret); + if (r == -ENOENT && proc_mounted() == 0) + return -ENOSYS; + + return r; +} + _public_ int sd_id128_get_boot(sd_id128_t *ret) { static thread_local sd_id128_t saved_boot_id = {}; int r; if (sd_id128_is_null(saved_boot_id)) { - r = id128_read("/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID | ID128_REFUSE_NULL, &saved_boot_id); - if (r == -ENOENT && proc_mounted() == 0) - return -ENOSYS; + r = id128_get_boot(&saved_boot_id); if (r < 0) return r; } @@ -192,7 +203,6 @@ static int get_invocation_from_keyring(sd_id128_t *ret) { char *d, *p, *g, *u, *e; unsigned long perms; key_serial_t key; - size_t sz = 256; uid_t uid; gid_t gid; int r, c; @@ -211,24 +221,9 @@ static int get_invocation_from_keyring(sd_id128_t *ret) { return -errno; } - for (;;) { - description = new(char, sz); - if (!description) - return -ENOMEM; - - c = keyctl(KEYCTL_DESCRIBE, key, (unsigned long) description, sz, 0); - if (c < 0) - return -errno; - - if ((size_t) c <= sz) - break; - - sz = c; - free(description); - } - - /* The kernel returns a final NUL in the string, verify that. */ - assert(description[c-1] == 0); + r = keyring_describe(key, &description); + if (r < 0) + return r; /* Chop off the final description string */ d = strrchr(description, ';'); @@ -380,3 +375,16 @@ _public_ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret) return sd_id128_get_app_specific(id, app_id, ret); } + +_public_ int sd_id128_get_invocation_app_specific(sd_id128_t app_id, sd_id128_t *ret) { + sd_id128_t id; + int r; + + assert_return(ret, -EINVAL); + + r = sd_id128_get_invocation(&id); + if (r < 0) + return r; + + return sd_id128_get_app_specific(id, app_id, ret); +} diff --git a/src/libnm-systemd-core/src/systemd/_sd-common.h b/src/libnm-systemd-core/src/systemd/_sd-common.h index d4381d90f4..5792dd8106 100644 --- a/src/libnm-systemd-core/src/systemd/_sd-common.h +++ b/src/libnm-systemd-core/src/systemd/_sd-common.h @@ -45,6 +45,10 @@ typedef void (*_sd_destroy_t)(void *userdata); # define _sd_pure_ __attribute__((__pure__)) #endif +#ifndef _sd_const_ +# define _sd_const_ __attribute__((__const__)) +#endif + /* Note that strictly speaking __deprecated__ has been available before GCC 6. However, starting with GCC 6 * it also works on enum values, which we are interested in. Since this is a developer-facing feature anyway * (as opposed to build engineer-facing), let's hence conditionalize this to gcc 6, given that the developers @@ -105,4 +109,11 @@ typedef void (*_sd_destroy_t)(void *userdata); _SD_##id##_INT64_MIN = INT64_MIN, \ _SD_##id##_INT64_MAX = INT64_MAX +/* In GCC 14 (C23) we can force enums to have the right types, and not solely rely on language extensions anymore */ +#if ((__GNUC__ >= 14) || (__STDC_VERSION__ >= 202311L)) && !defined(__cplusplus) +# define _SD_ENUM_TYPE_S64(id) id : int64_t +#else +# define _SD_ENUM_TYPE_S64(id) id +#endif + #endif diff --git a/src/libnm-systemd-core/src/systemd/sd-device.h b/src/libnm-systemd-core/src/systemd/sd-device.h index b67ec0f34d..f627ae6dae 100644 --- a/src/libnm-systemd-core/src/systemd/sd-device.h +++ b/src/libnm-systemd-core/src/systemd/sd-device.h @@ -34,7 +34,7 @@ typedef struct sd_device sd_device; typedef struct sd_device_enumerator sd_device_enumerator; typedef struct sd_device_monitor sd_device_monitor; -__extension__ typedef enum sd_device_action_t { +__extension__ typedef enum _SD_ENUM_TYPE_S64(sd_device_action_t) { SD_DEVICE_ADD, SD_DEVICE_REMOVE, SD_DEVICE_CHANGE, @@ -74,6 +74,7 @@ int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *su int sd_device_get_syspath(sd_device *device, const char **ret); int sd_device_get_subsystem(sd_device *device, const char **ret); +int sd_device_get_driver_subsystem(sd_device *device, const char **ret); int sd_device_get_devtype(sd_device *device, const char **ret); int sd_device_get_devnum(sd_device *device, dev_t *devnum); int sd_device_get_ifindex(sd_device *device, int *ifindex); @@ -85,21 +86,22 @@ int sd_device_get_sysnum(sd_device *device, const char **ret); int sd_device_get_action(sd_device *device, sd_device_action_t *ret); int sd_device_get_seqnum(sd_device *device, uint64_t *ret); int sd_device_get_diskseq(sd_device *device, uint64_t *ret); +int sd_device_get_device_id(sd_device *device, const char **ret); int sd_device_get_is_initialized(sd_device *device); int sd_device_get_usec_initialized(sd_device *device, uint64_t *ret); int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *ret); -const char *sd_device_get_tag_first(sd_device *device); -const char *sd_device_get_tag_next(sd_device *device); -const char *sd_device_get_current_tag_first(sd_device *device); -const char *sd_device_get_current_tag_next(sd_device *device); -const char *sd_device_get_devlink_first(sd_device *device); -const char *sd_device_get_devlink_next(sd_device *device); -const char *sd_device_get_property_first(sd_device *device, const char **value); -const char *sd_device_get_property_next(sd_device *device, const char **value); -const char *sd_device_get_sysattr_first(sd_device *device); -const char *sd_device_get_sysattr_next(sd_device *device); +const char* sd_device_get_tag_first(sd_device *device); +const char* sd_device_get_tag_next(sd_device *device); +const char* sd_device_get_current_tag_first(sd_device *device); +const char* sd_device_get_current_tag_next(sd_device *device); +const char* sd_device_get_devlink_first(sd_device *device); +const char* sd_device_get_devlink_next(sd_device *device); +const char* sd_device_get_property_first(sd_device *device, const char **value); +const char* sd_device_get_property_next(sd_device *device, const char **value); +const char* sd_device_get_sysattr_first(sd_device *device); +const char* sd_device_get_sysattr_next(sd_device *device); sd_device *sd_device_get_child_first(sd_device *device, const char **ret_suffix); sd_device *sd_device_get_child_next(sd_device *device, const char **ret_suffix); @@ -135,6 +137,7 @@ int sd_device_enumerator_add_nomatch_sysname(sd_device_enumerator *enumerator, c int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag); int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent); int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator); +int sd_device_enumerator_add_all_parents(sd_device_enumerator *enumerator); /* device monitor */ @@ -142,6 +145,9 @@ int sd_device_monitor_new(sd_device_monitor **ret); sd_device_monitor *sd_device_monitor_ref(sd_device_monitor *m); sd_device_monitor *sd_device_monitor_unref(sd_device_monitor *m); +int sd_device_monitor_get_fd(sd_device_monitor *m); +int sd_device_monitor_get_events(sd_device_monitor *m); +int sd_device_monitor_get_timeout(sd_device_monitor *m, uint64_t *ret); int sd_device_monitor_set_receive_buffer_size(sd_device_monitor *m, size_t size); int sd_device_monitor_attach_event(sd_device_monitor *m, sd_event *event); int sd_device_monitor_detach_event(sd_device_monitor *m); @@ -149,8 +155,10 @@ sd_event *sd_device_monitor_get_event(sd_device_monitor *m); sd_event_source *sd_device_monitor_get_event_source(sd_device_monitor *m); int sd_device_monitor_set_description(sd_device_monitor *m, const char *description); int sd_device_monitor_get_description(sd_device_monitor *m, const char **ret); +int sd_device_monitor_is_running(sd_device_monitor *m); int sd_device_monitor_start(sd_device_monitor *m, sd_device_monitor_handler_t callback, void *userdata); int sd_device_monitor_stop(sd_device_monitor *m); +int sd_device_monitor_receive(sd_device_monitor *m, sd_device **ret); int sd_device_monitor_filter_add_match_subsystem_devtype(sd_device_monitor *m, const char *subsystem, const char *devtype); int sd_device_monitor_filter_add_match_tag(sd_device_monitor *m, const char *tag); diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp-duid.h b/src/libnm-systemd-core/src/systemd/sd-dhcp-duid.h index b1d2772a3e..555b40e9d8 100644 --- a/src/libnm-systemd-core/src/systemd/sd-dhcp-duid.h +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp-duid.h @@ -3,7 +3,6 @@ #define foosddhcpduidhfoo /*** - Copyright © 2013 Intel Corporation. All rights reserved. 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 diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h index e18d57817f..d6bcceb2a2 100644 --- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h @@ -30,6 +30,7 @@ _SD_BEGIN_DECLARATIONS; typedef struct sd_dhcp6_lease sd_dhcp6_lease; +typedef struct sd_dns_resolver sd_dns_resolver; int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret); int sd_dhcp6_lease_get_t1(sd_dhcp6_lease *lease, uint64_t *ret); @@ -74,6 +75,7 @@ int sd_dhcp6_lease_get_pd_lifetime_timestamp( int sd_dhcp6_lease_has_pd_prefix(sd_dhcp6_lease *lease); int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret); +int sd_dhcp6_lease_get_dnr(sd_dhcp6_lease *lease, sd_dns_resolver **ret); int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret); int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret); int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret); diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-protocol.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-protocol.h index 78c80f7c7e..b1d9ca1ffa 100644 --- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-protocol.h +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-protocol.h @@ -165,8 +165,9 @@ enum { SD_DHCP6_OPTION_SLAP_QUAD = 140, /* RFC 8948 */ SD_DHCP6_OPTION_V6_DOTS_RI = 141, /* RFC 8973 */ SD_DHCP6_OPTION_V6_DOTS_ADDRESS = 142, /* RFC 8973 */ - SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF = 143 /* RFC 6153 */ - /* option codes 144-65535 are unassigned */ + SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF = 143, /* RFC 6153 */ + SD_DHCP6_OPTION_V6_DNR = 144 /* RFC 9463 */ + /* option codes 145-65535 are unassigned */ }; _SD_END_DECLARATIONS; diff --git a/src/libnm-systemd-core/src/systemd/sd-event.h b/src/libnm-systemd-core/src/systemd/sd-event.h index 49d6975967..1e19dd4eac 100644 --- a/src/libnm-systemd-core/src/systemd/sd-event.h +++ b/src/libnm-systemd-core/src/systemd/sd-event.h @@ -108,12 +108,12 @@ int sd_event_run(sd_event *e, uint64_t usec); int sd_event_loop(sd_event *e); int sd_event_exit(sd_event *e, int code); -int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec); +int sd_event_now(sd_event *e, clockid_t clock, uint64_t *ret); int sd_event_get_fd(sd_event *e); int sd_event_get_state(sd_event *e); -int sd_event_get_tid(sd_event *e, pid_t *tid); -int sd_event_get_exit_code(sd_event *e, int *code); +int sd_event_get_tid(sd_event *e, pid_t *ret); +int sd_event_get_exit_code(sd_event *e, int *ret); int sd_event_set_watchdog(sd_event *e, int b); int sd_event_get_watchdog(sd_event *e); int sd_event_get_iteration(sd_event *e, uint64_t *ret); @@ -123,33 +123,33 @@ sd_event_source* sd_event_source_ref(sd_event_source *s); sd_event_source* sd_event_source_unref(sd_event_source *s); sd_event_source* sd_event_source_disable_unref(sd_event_source *s); -sd_event *sd_event_source_get_event(sd_event_source *s); +sd_event* sd_event_source_get_event(sd_event_source *s); void* sd_event_source_get_userdata(sd_event_source *s); void* sd_event_source_set_userdata(sd_event_source *s, void *userdata); int sd_event_source_set_description(sd_event_source *s, const char *description); -int sd_event_source_get_description(sd_event_source *s, const char **description); +int sd_event_source_get_description(sd_event_source *s, const char **ret); int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback); int sd_event_source_get_pending(sd_event_source *s); -int sd_event_source_get_priority(sd_event_source *s, int64_t *priority); +int sd_event_source_get_priority(sd_event_source *s, int64_t *ret); int sd_event_source_set_priority(sd_event_source *s, int64_t priority); -int sd_event_source_get_enabled(sd_event_source *s, int *enabled); +int sd_event_source_get_enabled(sd_event_source *s, int *ret); int sd_event_source_set_enabled(sd_event_source *s, int enabled); int sd_event_source_get_io_fd(sd_event_source *s); int sd_event_source_set_io_fd(sd_event_source *s, int fd); int sd_event_source_get_io_fd_own(sd_event_source *s); int sd_event_source_set_io_fd_own(sd_event_source *s, int own); -int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events); +int sd_event_source_get_io_events(sd_event_source *s, uint32_t *ret); int sd_event_source_set_io_events(sd_event_source *s, uint32_t events); -int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents); -int sd_event_source_get_time(sd_event_source *s, uint64_t *usec); +int sd_event_source_get_io_revents(sd_event_source *s, uint32_t *ret); +int sd_event_source_get_time(sd_event_source *s, uint64_t *ret); int sd_event_source_set_time(sd_event_source *s, uint64_t usec); int sd_event_source_set_time_relative(sd_event_source *s, uint64_t usec); -int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec); +int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *ret); int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec); -int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock); +int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *ret); int sd_event_source_get_signal(sd_event_source *s); -int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid); +int sd_event_source_get_child_pid(sd_event_source *s, pid_t *ret); int sd_event_source_get_child_pidfd(sd_event_source *s); int sd_event_source_get_child_pidfd_own(sd_event_source *s); int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own); @@ -161,6 +161,7 @@ int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo int sd_event_source_send_child_signal(sd_event_source *s, int sig, const void *si, unsigned flags); #endif int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret); +int sd_event_source_get_inotify_path(sd_event_source *s, const char **ret); int sd_event_source_set_memory_pressure_type(sd_event_source *e, const char *ty); int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback); diff --git a/src/libnm-systemd-core/src/systemd/sd-id128.h b/src/libnm-systemd-core/src/systemd/sd-id128.h index a984a9d85e..7be690400d 100644 --- a/src/libnm-systemd-core/src/systemd/sd-id128.h +++ b/src/libnm-systemd-core/src/systemd/sd-id128.h @@ -37,8 +37,8 @@ union sd_id128 { #define SD_ID128_STRING_MAX 33U #define SD_ID128_UUID_STRING_MAX 37U -char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]); -char *sd_id128_to_uuid_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_UUID_STRING_MAX]); +char* sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]); +char* sd_id128_to_uuid_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_UUID_STRING_MAX]); int sd_id128_from_string(const char *s, sd_id128_t *ret); #define SD_ID128_TO_STRING(id) sd_id128_to_string((id), (char[SD_ID128_STRING_MAX]) {}) @@ -53,6 +53,7 @@ int sd_id128_get_invocation(sd_id128_t *ret); int sd_id128_get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret); int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret); int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret); +int sd_id128_get_invocation_app_specific(sd_id128_t app_id, sd_id128_t *ret); #define SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ @@ -116,24 +117,24 @@ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret); #define SD_ID128_MAKE_UUID_STR(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \ #a #b #c #d "-" #e #f "-" #g #h "-" #i #j "-" #k #l #m #n #o #p -_sd_pure_ static __inline__ int sd_id128_equal(sd_id128_t a, sd_id128_t b) { +_sd_const_ static __inline__ int sd_id128_equal(sd_id128_t a, sd_id128_t b) { return a.qwords[0] == b.qwords[0] && a.qwords[1] == b.qwords[1]; } int sd_id128_string_equal(const char *s, sd_id128_t id); -_sd_pure_ static __inline__ int sd_id128_is_null(sd_id128_t a) { +_sd_const_ static __inline__ int sd_id128_is_null(sd_id128_t a) { return a.qwords[0] == 0 && a.qwords[1] == 0; } -_sd_pure_ static __inline__ int sd_id128_is_allf(sd_id128_t a) { +_sd_const_ static __inline__ int sd_id128_is_allf(sd_id128_t a) { return a.qwords[0] == UINT64_C(0xFFFFFFFFFFFFFFFF) && a.qwords[1] == UINT64_C(0xFFFFFFFFFFFFFFFF); } #define SD_ID128_NULL ((const sd_id128_t) { .qwords = { 0, 0 }}) #define SD_ID128_ALLF ((const sd_id128_t) { .qwords = { UINT64_C(0xFFFFFFFFFFFFFFFF), UINT64_C(0xFFFFFFFFFFFFFFFF) }}) -_sd_pure_ static __inline__ int sd_id128_in_setv(sd_id128_t a, va_list ap) { +_sd_const_ static __inline__ int sd_id128_in_setv(sd_id128_t a, va_list ap) { for (;;) { sd_id128_t b = va_arg(ap, sd_id128_t); @@ -145,7 +146,7 @@ _sd_pure_ static __inline__ int sd_id128_in_setv(sd_id128_t a, va_list ap) { } } -_sd_pure_ static __inline__ int sd_id128_in_set_sentinel(sd_id128_t a, ...) { +_sd_const_ static __inline__ int sd_id128_in_set_sentinel(sd_id128_t a, ...) { va_list ap; int r; diff --git a/src/libnm-systemd-core/src/systemd/sd-ndisc.h b/src/libnm-systemd-core/src/systemd/sd-ndisc.h index a5ccd5f644..85fcf6bc03 100644 --- a/src/libnm-systemd-core/src/systemd/sd-ndisc.h +++ b/src/libnm-systemd-core/src/systemd/sd-ndisc.h @@ -26,52 +26,37 @@ #include #include "sd-event.h" +#include "sd-ndisc-neighbor.h" +#include "sd-ndisc-protocol.h" +#include "sd-ndisc-redirect.h" +#include "sd-ndisc-router.h" #include "_sd-common.h" _SD_BEGIN_DECLARATIONS; -/* Neighbor Discovery Options, RFC 4861, Section 4.6 and - * https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5 */ -enum { - SD_NDISC_OPTION_SOURCE_LL_ADDRESS = 1, - SD_NDISC_OPTION_TARGET_LL_ADDRESS = 2, - SD_NDISC_OPTION_PREFIX_INFORMATION = 3, - SD_NDISC_OPTION_MTU = 5, - SD_NDISC_OPTION_ROUTE_INFORMATION = 24, - SD_NDISC_OPTION_RDNSS = 25, - SD_NDISC_OPTION_FLAGS_EXTENSION = 26, - SD_NDISC_OPTION_DNSSL = 31, - SD_NDISC_OPTION_CAPTIVE_PORTAL = 37, - SD_NDISC_OPTION_PREF64 = 38 -}; - -/* Route preference, RFC 4191, Section 2.1 */ -enum { - SD_NDISC_PREFERENCE_LOW = 3U, - SD_NDISC_PREFERENCE_MEDIUM = 0U, - SD_NDISC_PREFERENCE_HIGH = 1U -}; - typedef struct sd_ndisc sd_ndisc; -typedef struct sd_ndisc_router sd_ndisc_router; -__extension__ typedef enum sd_ndisc_event_t { +__extension__ typedef enum _SD_ENUM_TYPE_S64(sd_ndisc_event_t) { SD_NDISC_EVENT_TIMEOUT, SD_NDISC_EVENT_ROUTER, + SD_NDISC_EVENT_NEIGHBOR, + SD_NDISC_EVENT_REDIRECT, _SD_NDISC_EVENT_MAX, _SD_NDISC_EVENT_INVALID = -EINVAL, _SD_ENUM_FORCE_S64(NDISC_EVENT) } sd_ndisc_event_t; -typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router *rt, void *userdata); +typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata); int sd_ndisc_new(sd_ndisc **ret); sd_ndisc *sd_ndisc_ref(sd_ndisc *nd); sd_ndisc *sd_ndisc_unref(sd_ndisc *nd); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); int sd_ndisc_start(sd_ndisc *nd); int sd_ndisc_stop(sd_ndisc *nd); +int sd_ndisc_is_running(sd_ndisc *nd); int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority); int sd_ndisc_detach_event(sd_ndisc *nd); @@ -81,69 +66,9 @@ int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t cb, void *userdata); int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index); int sd_ndisc_set_ifname(sd_ndisc *nd, const char *interface_name); int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret); +int sd_ndisc_set_link_local_address(sd_ndisc *nd, const struct in6_addr *addr); int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr); -sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt); -sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt); - -int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret); -int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); -int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size); - -int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret); -int sd_ndisc_router_get_icmp6_ratelimit(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret); -int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); -int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret); - -/* Generic option access */ -int sd_ndisc_router_option_rewind(sd_ndisc_router *rt); -int sd_ndisc_router_option_next(sd_ndisc_router *rt); -int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret); -int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type); -int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size); - -/* Specific option access: SD_NDISC_OPTION_PREFIX_INFORMATION */ -int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_prefix_get_valid_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); -int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); -int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret); -int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret); -int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret); - -/* Specific option access: SD_NDISC_OPTION_ROUTE_INFORMATION */ -int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_route_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); -int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret); -int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret); -int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret); - -/* Specific option access: SD_NDISC_OPTION_RDNSS */ -int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret); -int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_rdnss_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); - -/* Specific option access: SD_NDISC_OPTION_DNSSL */ -int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret); -int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_dnssl_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); - -/* Specific option access: SD_NDISC_OPTION_CAPTIVE_PORTAL */ -int sd_ndisc_router_captive_portal_get_uri(sd_ndisc_router *rt, const char **ret, size_t *ret_size); - -/* Specific option access: SD_NDISC_OPTION_PREF64 */ -int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret); -int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, unsigned *ret); -int sd_ndisc_router_prefix64_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_prefix64_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref); - _SD_END_DECLARATIONS; #endif diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.c b/src/libnm-systemd-shared/src/basic/alloc-util.c index fc98610a0f..96cf27dc37 100644 --- a/src/libnm-systemd-shared/src/basic/alloc-util.c +++ b/src/libnm-systemd-shared/src/basic/alloc-util.c @@ -43,7 +43,7 @@ void* greedy_realloc( size_t need, size_t size) { - size_t a, newalloc; + size_t newalloc; void *q; assert(p); @@ -60,14 +60,13 @@ void* greedy_realloc( return NULL; newalloc = need * 2; - if (size_multiply_overflow(newalloc, size)) + if (!MUL_ASSIGN_SAFE(&newalloc, size)) return NULL; - a = newalloc * size; - if (a < 64) /* Allocate at least 64 bytes */ - a = 64; + if (newalloc < 64) /* Allocate at least 64 bytes */ + newalloc = 64; - q = realloc(*p, a); + q = realloc(*p, newalloc); if (!q) return NULL; diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.h b/src/libnm-systemd-shared/src/basic/alloc-util.h index c215c33f4b..ba71298287 100644 --- a/src/libnm-systemd-shared/src/basic/alloc-util.h +++ b/src/libnm-systemd-shared/src/basic/alloc-util.h @@ -26,23 +26,23 @@ typedef void* (*mfree_func_t)(void *p); #define alloca_safe(n) \ ({ \ - size_t _nn_ = n; \ + size_t _nn_ = (n); \ assert(_nn_ <= ALLOCA_MAX); \ alloca(_nn_ == 0 ? 1 : _nn_); \ }) \ #define newa(t, n) \ ({ \ - size_t _n_ = n; \ - assert(!size_multiply_overflow(sizeof(t), _n_)); \ - (t*) alloca_safe(sizeof(t)*_n_); \ + size_t _n_ = (n); \ + assert_se(MUL_ASSIGN_SAFE(&_n_, sizeof(t))); \ + (t*) alloca_safe(_n_); \ }) #define newa0(t, n) \ ({ \ - size_t _n_ = n; \ - assert(!size_multiply_overflow(sizeof(t), _n_)); \ - (t*) alloca0((sizeof(t)*_n_)); \ + size_t _n_ = (n); \ + assert_se(MUL_ASSIGN_SAFE(&_n_, sizeof(t))); \ + (t*) alloca0(_n_); \ }) #define newdup(t, p, n) ((t*) memdup_multiply(p, n, sizeof(t))) @@ -155,7 +155,10 @@ void* greedy_realloc_append(void **p, size_t *n_p, const void *from, size_t n_fr greedy_realloc0((void**) &(array), (need), sizeof((array)[0])) #define GREEDY_REALLOC_APPEND(array, n_array, from, n_from) \ - greedy_realloc_append((void**) &(array), (size_t*) &(n_array), (from), (n_from), sizeof((array)[0])) + ({ \ + const typeof(*(array)) *_from_ = (from); \ + greedy_realloc_append((void**) &(array), &(n_array), _from_, (n_from), sizeof((array)[0])); \ + }) #define alloca0(n) \ ({ \ diff --git a/src/libnm-systemd-shared/src/basic/arphrd-util.h b/src/libnm-systemd-shared/src/basic/arphrd-util.h index 33f5694abd..0fd75a0436 100644 --- a/src/libnm-systemd-shared/src/basic/arphrd-util.h +++ b/src/libnm-systemd-shared/src/basic/arphrd-util.h @@ -4,7 +4,7 @@ #include #include -const char *arphrd_to_name(int id); +const char* arphrd_to_name(int id); int arphrd_from_name(const char *name); size_t arphrd_to_hw_addr_len(uint16_t arphrd); diff --git a/src/libnm-systemd-shared/src/basic/bitfield.h b/src/libnm-systemd-shared/src/basic/bitfield.h new file mode 100644 index 0000000000..048e08d753 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/bitfield.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "macro.h" + +/* Bit index (0-based) to mask of specified type. Assertion failure if index is out of range. */ +#define _INDEX_TO_MASK(type, i, uniq) \ + ({ \ + int UNIQ_T(_i, uniq) = (i); \ + assert(UNIQ_T(_i, uniq) < (int)sizeof(type) * 8); \ + ((type)1) << UNIQ_T(_i, uniq); \ + }) +#define INDEX_TO_MASK(type, i) \ + ({ \ + assert_cc(sizeof(type) <= sizeof(unsigned long long)); \ + assert_cc(__builtin_choose_expr(__builtin_constant_p(i), i, 0) < (int)(sizeof(type) * 8)); \ + __builtin_choose_expr(__builtin_constant_p(i), \ + ((type)1) << (i), \ + _INDEX_TO_MASK(type, i, UNIQ)); \ + }) + +/* Builds a mask of specified type with multiple bits set. Note the result will not be constant, even if all + * indexes are constant. */ +#define INDEXES_TO_MASK(type, ...) \ + UNIQ_INDEXES_TO_MASK(type, UNIQ, ##__VA_ARGS__) +#define UNIQ_INDEXES_TO_MASK(type, uniq, ...) \ + ({ \ + typeof(type) UNIQ_T(_mask, uniq) = (type)0; \ + int UNIQ_T(_i, uniq); \ + FOREACH_ARGUMENT(UNIQ_T(_i, uniq), ##__VA_ARGS__) \ + UNIQ_T(_mask, uniq) |= INDEX_TO_MASK(type, UNIQ_T(_i, uniq)); \ + UNIQ_T(_mask, uniq); \ + }) + +/* Same as the FLAG macros, but accept a 0-based bit index instead of a mask. Results in assertion failure if + * index is out of range for the type. */ +#define SET_BIT(bits, i) SET_FLAG(bits, INDEX_TO_MASK(typeof(bits), i), true) +#define CLEAR_BIT(bits, i) SET_FLAG(bits, INDEX_TO_MASK(typeof(bits), i), false) +#define BIT_SET(bits, i) FLAGS_SET(bits, INDEX_TO_MASK(typeof(bits), i)) + +/* As above, but accepts multiple indexes. Note the result will not be constant, even if all indexes are + * constant. */ +#define SET_BITS(bits, ...) SET_FLAG(bits, INDEXES_TO_MASK(typeof(bits), ##__VA_ARGS__), true) +#define CLEAR_BITS(bits, ...) SET_FLAG(bits, INDEXES_TO_MASK(typeof(bits), ##__VA_ARGS__), false) +#define BITS_SET(bits, ...) FLAGS_SET(bits, INDEXES_TO_MASK(typeof(bits), ##__VA_ARGS__)) + +/* Iterate through each set bit. Index is 0-based and type int. */ +#define BIT_FOREACH(index, bits) _BIT_FOREACH(index, bits, UNIQ) +#define _BIT_FOREACH(index, bits, uniq) \ + for (int UNIQ_T(_last, uniq) = -1, index; \ + (index = BIT_NEXT_SET(bits, UNIQ_T(_last, uniq))) >= 0; \ + UNIQ_T(_last, uniq) = index) + +/* Find the next set bit after 0-based index 'prev'. Result is 0-based index of next set bit, or -1 if no + * more bits are set. */ +#define BIT_FIRST_SET(bits) BIT_NEXT_SET(bits, -1) +#define BIT_NEXT_SET(bits, prev) \ + UNIQ_BIT_NEXT_SET(bits, prev, UNIQ) +#define UNIQ_BIT_NEXT_SET(bits, prev, uniq) \ + ({ \ + typeof(bits) UNIQ_T(_bits, uniq) = (bits); \ + int UNIQ_T(_prev, uniq) = (prev); \ + int UNIQ_T(_next, uniq); \ + _BIT_NEXT_SET(UNIQ_T(_bits, uniq), \ + UNIQ_T(_prev, uniq), \ + UNIQ_T(_next, uniq)); \ + }) +#define _BIT_NEXT_SET(bits, prev, next) \ + ((int)(prev + 1) == (int)sizeof(bits) * 8 \ + ? -1 /* Prev index was msb. */ \ + : ((next = __builtin_ffsll(((unsigned long long)(bits)) >> (prev + 1))) == 0 \ + ? -1 /* No more bits set. */ \ + : prev + next)) diff --git a/src/libnm-systemd-shared/src/basic/cgroup-util.h b/src/libnm-systemd-shared/src/basic/cgroup-util.h index 244f3b657b..77294779fe 100644 --- a/src/libnm-systemd-shared/src/basic/cgroup-util.h +++ b/src/libnm-systemd-shared/src/basic/cgroup-util.h @@ -180,20 +180,33 @@ typedef enum CGroupUnified { * generate paths with multiple adjacent / removed. */ +int cg_path_open(const char *controller, const char *path); +int cg_cgroupid_open(int fsfd, uint64_t id); + +int cg_path_from_cgroupid(int cgroupfs_fd, uint64_t id, char **ret); +int cg_get_cgroupid_at(int dfd, const char *path, uint64_t *ret); +static inline int cg_path_get_cgroupid(const char *path, uint64_t *ret) { + return cg_get_cgroupid_at(AT_FDCWD, path, ret); +} +static inline int cg_fd_get_cgroupid(int fd, uint64_t *ret) { + return cg_get_cgroupid_at(fd, NULL, ret); +} + +typedef enum CGroupFlags { + CGROUP_SIGCONT = 1 << 0, + CGROUP_IGNORE_SELF = 1 << 1, + CGROUP_DONT_SKIP_UNMAPPED = 1 << 2, + CGROUP_NO_PIDFD = 1 << 3, +} CGroupFlags; + int cg_enumerate_processes(const char *controller, const char *path, FILE **ret); -int cg_read_pid(FILE *f, pid_t *ret); -int cg_read_pidref(FILE *f, PidRef *ret); +int cg_read_pid(FILE *f, pid_t *ret, CGroupFlags flags); +int cg_read_pidref(FILE *f, PidRef *ret, CGroupFlags flags); int cg_read_event(const char *controller, const char *path, const char *event, char **ret); int cg_enumerate_subgroups(const char *controller, const char *path, DIR **ret); int cg_read_subgroup(DIR *d, char **ret); -typedef enum CGroupFlags { - CGROUP_SIGCONT = 1 << 0, - CGROUP_IGNORE_SELF = 1 << 1, - CGROUP_REMOVE = 1 << 2, -} CGroupFlags; - typedef int (*cg_kill_log_func_t)(const PidRef *pid, int sig, void *userdata); int cg_kill(const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata); @@ -209,8 +222,6 @@ int cg_get_path_and_check(const char *controller, const char *path, const char * int cg_pid_get_path(const char *controller, pid_t pid, char **ret); int cg_pidref_get_path(const char *controller, const PidRef *pidref, char **ret); -int cg_rmdir(const char *controller, const char *path); - int cg_is_threaded(const char *path); int cg_is_delegated(const char *path); @@ -258,15 +269,11 @@ int cg_get_xattr_malloc(const char *path, const char *name, char **ret); int cg_get_xattr_bool(const char *path, const char *name); int cg_remove_xattr(const char *path, const char *name); -int cg_install_release_agent(const char *controller, const char *agent); -int cg_uninstall_release_agent(const char *controller); - int cg_is_empty(const char *controller, const char *path); int cg_is_empty_recursive(const char *controller, const char *path); int cg_get_root_path(char **path); -int cg_path_get_cgroupid(const char *path, uint64_t *ret); int cg_path_get_session(const char *path, char **ret_session); int cg_path_get_owner_uid(const char *path, uid_t *ret_uid); int cg_path_get_unit(const char *path, char **ret_unit); @@ -280,7 +287,9 @@ int cg_shift_path(const char *cgroup, const char *cached_root, const char **ret_ int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **ret_cgroup); int cg_pid_get_session(pid_t pid, char **ret_session); +int cg_pidref_get_session(const PidRef *pidref, char **ret); int cg_pid_get_owner_uid(pid_t pid, uid_t *ret_uid); +int cg_pidref_get_owner_uid(const PidRef *pidref, uid_t *ret); int cg_pid_get_unit(pid_t pid, char **ret_unit); int cg_pidref_get_unit(const PidRef *pidref, char **ret); int cg_pid_get_user_unit(pid_t pid, char **ret_unit); @@ -292,14 +301,12 @@ int cg_path_decode_unit(const char *cgroup, char **ret_unit); bool cg_needs_escape(const char *p); int cg_escape(const char *p, char **ret); -char *cg_unescape(const char *p) _pure_; +char* cg_unescape(const char *p) _pure_; bool cg_controller_is_valid(const char *p); int cg_slice_to_path(const char *unit, char **ret); -typedef const char* (*cg_migrate_callback_t)(CGroupMask mask, void *userdata); - int cg_mask_supported(CGroupMask *ret); int cg_mask_supported_subtree(const char *root, CGroupMask *ret); int cg_mask_from_string(const char *s, CGroupMask *ret); @@ -352,5 +359,10 @@ typedef union { uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(uint64_t)]; } cg_file_handle; -#define CG_FILE_HANDLE_INIT { .file_handle.handle_bytes = sizeof(uint64_t) } +#define CG_FILE_HANDLE_INIT \ + (cg_file_handle) { \ + .file_handle.handle_bytes = sizeof(uint64_t), \ + .file_handle.handle_type = FILEID_KERNFS, \ + } + #define CG_FILE_HANDLE_CGROUPID(fh) (*(uint64_t*) (fh).file_handle.f_handle) diff --git a/src/libnm-systemd-shared/src/basic/chase.h b/src/libnm-systemd-shared/src/basic/chase.h index cfc714b9f7..eda7cad0b7 100644 --- a/src/libnm-systemd-shared/src/basic/chase.h +++ b/src/libnm-systemd-shared/src/basic/chase.h @@ -27,12 +27,10 @@ typedef enum ChaseFlags { * also points to the result path even if this flag is set. * When this specified, chase() will succeed with 1 even if the * file points to the last path component does not exist. */ - CHASE_MKDIR_0755 = 1 << 11, /* Create any missing parent directories in the given path. This - * needs to be set with CHASE_NONEXISTENT and/or CHASE_PARENT. - * Note, chase_and_open() or friends always add CHASE_PARENT flag - * when internally call chase(), hence CHASE_MKDIR_0755 can be - * safely set without CHASE_NONEXISTENT and CHASE_PARENT. */ + CHASE_MKDIR_0755 = 1 << 11, /* Create any missing directories in the given path. */ CHASE_EXTRACT_FILENAME = 1 << 12, /* Only return the last component of the resolved path */ + CHASE_MUST_BE_DIRECTORY = 1 << 13, /* Fail if returned inode fd is not a dir */ + CHASE_MUST_BE_REGULAR = 1 << 14, /* Fail if returned inode fd is not a regular file */ } ChaseFlags; bool unsafe_transition(const struct stat *a, const struct stat *b); diff --git a/src/libnm-systemd-shared/src/basic/chattr-util.c b/src/libnm-systemd-shared/src/basic/chattr-util.c new file mode 100644 index 0000000000..39fdf970a7 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/chattr-util.c @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include + +#include "bitfield.h" +#include "chattr-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "macro.h" +#include "string-util.h" + +int chattr_full( + int dir_fd, + const char *path, + unsigned value, + unsigned mask, + unsigned *ret_previous, + unsigned *ret_final, + ChattrApplyFlags flags) { + + _cleanup_close_ int fd = -EBADF; + unsigned old_attr, new_attr; + int set_flags_errno = 0; + struct stat st; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + + fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return fd; + + if (fstat(fd, &st) < 0) + return -errno; + + /* Explicitly check whether this is a regular file or directory. If it is anything else (such + * as a device node or fifo), then the ioctl will not hit the file systems but possibly + * drivers, where the ioctl might have different effects. Notably, DRM is using the same + * ioctl() number. */ + + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) + return -ENOTTY; + + if (mask == 0 && !ret_previous && !ret_final) + return 0; + + if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) + return -errno; + + new_attr = (old_attr & ~mask) | (value & mask); + if (new_attr == old_attr) { + if (ret_previous) + *ret_previous = old_attr; + if (ret_final) + *ret_final = old_attr; + return 0; + } + + if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) >= 0) { + unsigned attr; + + /* Some filesystems (BTRFS) silently fail when a flag cannot be set. Let's make sure our + * changes actually went through by querying the flags again and verifying they're equal to + * the flags we tried to configure. */ + + if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0) + return -errno; + + if (new_attr == attr) { + if (ret_previous) + *ret_previous = old_attr; + if (ret_final) + *ret_final = new_attr; + return 1; + } + + /* Trigger the fallback logic. */ + errno = EINVAL; + } + + if ((errno != EINVAL && !ERRNO_IS_NOT_SUPPORTED(errno)) || + !FLAGS_SET(flags, CHATTR_FALLBACK_BITWISE)) + return -errno; + + /* When -EINVAL is returned, we assume that incompatible attributes are simultaneously + * specified. E.g., compress(c) and nocow(C) attributes cannot be set to files on btrfs. + * As a fallback, let's try to set attributes one by one. + * + * Also, when we get EOPNOTSUPP (or a similar error code) we assume a flag might just not be + * supported, and we can ignore it too */ + + unsigned current_attr = old_attr; + + BIT_FOREACH(i, mask) { + unsigned new_one, mask_one = 1u << i; + + new_one = UPDATE_FLAG(current_attr, mask_one, FLAGS_SET(value, mask_one)); + if (new_one == current_attr) + continue; + + if (ioctl(fd, FS_IOC_SETFLAGS, &new_one) < 0) { + if (!ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) + return -errno; + + log_full_errno(FLAGS_SET(flags, CHATTR_WARN_UNSUPPORTED_FLAGS) ? LOG_WARNING : LOG_DEBUG, + errno, + "Unable to set file attribute 0x%x on %s, ignoring: %m", mask_one, strna(path)); + + /* Ensures that we record whether only EOPNOTSUPP&friends are encountered, or if a more serious + * error (thus worth logging at a different level, etc) was seen too. */ + if (set_flags_errno == 0 || !ERRNO_IS_NOT_SUPPORTED(errno)) + set_flags_errno = -errno; + + continue; + } + + if (ioctl(fd, FS_IOC_GETFLAGS, ¤t_attr) < 0) + return -errno; + } + + if (ret_previous) + *ret_previous = old_attr; + if (ret_final) + *ret_final = current_attr; + + /* -ENOANO indicates that some attributes cannot be set. ERRNO_IS_NOT_SUPPORTED indicates that all + * encountered failures were due to flags not supported by the FS, so return a specific error in + * that case, so callers can handle it properly (e.g.: tmpfiles.d can use debug level logging). */ + return current_attr == new_attr ? 1 : ERRNO_IS_NOT_SUPPORTED(set_flags_errno) ? set_flags_errno : -ENOANO; +} + +int read_attr_fd(int fd, unsigned *ret) { + struct stat st; + + assert(fd >= 0); + assert(ret); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) + return -ENOTTY; + + _cleanup_close_ int fd_close = -EBADF; + fd = fd_reopen_condition(fd, O_RDONLY|O_CLOEXEC|O_NOCTTY, O_PATH, &fd_close); /* drop O_PATH if it is set */ + if (fd < 0) + return fd; + + return RET_NERRNO(ioctl(fd, FS_IOC_GETFLAGS, ret)); +} + +int read_attr_at(int dir_fd, const char *path, unsigned *ret) { + _cleanup_close_ int fd_close = -EBADF; + int fd; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(ret); + + if (isempty(path) && dir_fd != AT_FDCWD) + fd = dir_fd; + else { + fd_close = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd_close < 0) + return fd_close; + + fd = fd_close; + } + + return read_attr_fd(fd, ret); +} diff --git a/src/libnm-systemd-shared/src/basic/chattr-util.h b/src/libnm-systemd-shared/src/basic/chattr-util.h new file mode 100644 index 0000000000..1fe38e32b1 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/chattr-util.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include +#include +#include + +#include "missing_fs.h" + +/* The chattr() flags to apply when creating a new file *before* writing to it. In particular, flags such as + * FS_NOCOW_FL don't work if applied a-posteriori. All other flags are fine (or even necessary, think + * FS_IMMUTABLE_FL!) to apply after writing to the files. */ +#define CHATTR_EARLY_FL \ + (FS_NOATIME_FL | \ + FS_COMPR_FL | \ + FS_NOCOW_FL | \ + FS_NOCOMP_FL | \ + FS_PROJINHERIT_FL) + +#define CHATTR_ALL_FL \ + (FS_NOATIME_FL | \ + FS_SYNC_FL | \ + FS_DIRSYNC_FL | \ + FS_APPEND_FL | \ + FS_COMPR_FL | \ + FS_NODUMP_FL | \ + FS_EXTENT_FL | \ + FS_IMMUTABLE_FL | \ + FS_JOURNAL_DATA_FL | \ + FS_SECRM_FL | \ + FS_UNRM_FL | \ + FS_NOTAIL_FL | \ + FS_TOPDIR_FL | \ + FS_NOCOW_FL | \ + FS_PROJINHERIT_FL) + +typedef enum ChattrApplyFlags { + CHATTR_FALLBACK_BITWISE = 1 << 0, + CHATTR_WARN_UNSUPPORTED_FLAGS = 1 << 1, +} ChattrApplyFlags; + +int chattr_full(int dir_fd, const char *path, unsigned value, unsigned mask, unsigned *ret_previous, unsigned *ret_final, ChattrApplyFlags flags); +static inline int chattr_at(int dir_fd, const char *path, unsigned value, unsigned mask, unsigned *previous) { + return chattr_full(dir_fd, path, value, mask, previous, NULL, 0); +} +static inline int chattr_fd(int fd, unsigned value, unsigned mask, unsigned *previous) { + return chattr_full(fd, NULL, value, mask, previous, NULL, 0); +} +static inline int chattr_path(const char *path, unsigned value, unsigned mask, unsigned *previous) { + return chattr_full(AT_FDCWD, path, value, mask, previous, NULL, 0); +} + +int read_attr_fd(int fd, unsigned *ret); +int read_attr_at(int dir_fd, const char *path, unsigned *ret); + +/* Combination of chattr flags, that should be appropriate for secrets stored on disk: Secure Remove + + * Exclusion from Dumping + Synchronous Writing (i.e. not caching in memory) + In-Place Updating (i.e. not + * spurious copies). */ +#define CHATTR_SECRET_FLAGS (FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL) + +static inline int chattr_secret(int fd, ChattrApplyFlags flags) { + return chattr_full(fd, NULL, CHATTR_SECRET_FLAGS, CHATTR_SECRET_FLAGS, NULL, NULL, flags|CHATTR_FALLBACK_BITWISE); +} diff --git a/src/libnm-systemd-shared/src/basic/constants.h b/src/libnm-systemd-shared/src/basic/constants.h index 6bb5f3c281..93e6efbdca 100644 --- a/src/libnm-systemd-shared/src/basic/constants.h +++ b/src/libnm-systemd-shared/src/basic/constants.h @@ -42,9 +42,6 @@ #define DEFAULT_START_LIMIT_INTERVAL (10*USEC_PER_SEC) #define DEFAULT_START_LIMIT_BURST 5 -/* Wait for 1.5 seconds at maximum for freeze operation */ -#define FREEZE_TIMEOUT (1500 * USEC_PER_MSEC) - /* The default time after which exit-on-idle services exit. This * should be kept lower than the watchdog timeout, because otherwise * the watchdog pings will keep the loop busy. */ @@ -67,18 +64,12 @@ "/usr/local/lib/" n "\0" \ "/usr/lib/" n "\0" -#define CONF_PATHS_USR(n) \ +#define CONF_PATHS(n) \ "/etc/" n, \ "/run/" n, \ "/usr/local/lib/" n, \ "/usr/lib/" n -#define CONF_PATHS(n) \ - CONF_PATHS_USR(n) - -#define CONF_PATHS_USR_STRV(n) \ - STRV_MAKE(CONF_PATHS_USR(n)) - #define CONF_PATHS_STRV(n) \ STRV_MAKE(CONF_PATHS(n)) @@ -94,4 +85,5 @@ /* Path where systemd-oomd listens for varlink connections from user managers to report changes in ManagedOOM settings. */ #define VARLINK_ADDR_PATH_MANAGED_OOM_USER "/run/systemd/oom/io.systemd.ManagedOOM" -#define KERNEL_BASELINE_VERSION "4.15" +/* Recommended baseline - see README for details */ +#define KERNEL_BASELINE_VERSION "5.7" diff --git a/src/libnm-systemd-shared/src/basic/devnum-util.c b/src/libnm-systemd-shared/src/basic/devnum-util.c index f82e13bdb0..652740cea7 100644 --- a/src/libnm-systemd-shared/src/basic/devnum-util.c +++ b/src/libnm-systemd-shared/src/basic/devnum-util.c @@ -58,21 +58,18 @@ int device_path_make_major_minor(mode_t mode, dev_t devnum, char **ret) { } int device_path_make_inaccessible(mode_t mode, char **ret) { - char *s; + const char *s; assert(ret); if (S_ISCHR(mode)) - s = strdup("/run/systemd/inaccessible/chr"); + s = "/run/systemd/inaccessible/chr"; else if (S_ISBLK(mode)) - s = strdup("/run/systemd/inaccessible/blk"); + s = "/run/systemd/inaccessible/blk"; else return -ENODEV; - if (!s) - return -ENOMEM; - *ret = s; - return 0; + return strdup_to(ret, s); } int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret) { diff --git a/src/libnm-systemd-shared/src/basic/env-util.c b/src/libnm-systemd-shared/src/basic/env-util.c index 6061edb904..99ce1a1842 100644 --- a/src/libnm-systemd-shared/src/basic/env-util.c +++ b/src/libnm-systemd-shared/src/basic/env-util.c @@ -18,6 +18,7 @@ #include "stdio-util.h" #include "string-util.h" #include "strv.h" +#include "syslog-util.h" #include "utf8.h" /* We follow bash for the character set. Different shells have different rules. */ @@ -265,7 +266,7 @@ static bool env_entry_has_name(const char *entry, const char *name) { return *t == '='; } -char **strv_env_delete(char **x, size_t n_lists, ...) { +char** strv_env_delete(char **x, size_t n_lists, ...) { size_t n, i = 0; _cleanup_strv_free_ char **t = NULL; va_list ap; @@ -309,19 +310,17 @@ char **strv_env_delete(char **x, size_t n_lists, ...) { return TAKE_PTR(t); } -char **strv_env_unset(char **l, const char *p) { - char **f, **t; +char** strv_env_unset(char **l, const char *p) { + assert(p); if (!l) return NULL; - assert(p); - /* Drops every occurrence of the env var setting p in the * string list. Edits in-place. */ + char **f, **t; for (f = t = l; *f; f++) { - if (env_match(*f, p)) { free(*f); continue; @@ -334,14 +333,13 @@ char **strv_env_unset(char **l, const char *p) { return l; } -char **strv_env_unset_many(char **l, ...) { - char **f, **t; - +char** strv_env_unset_many_internal(char **l, ...) { if (!l) return NULL; /* Like strv_env_unset() but applies many at once. Edits in-place. */ + char **f, **t; for (f = t = l; *f; f++) { bool found = false; const char *p; @@ -349,12 +347,11 @@ char **strv_env_unset_many(char **l, ...) { va_start(ap, l); - while ((p = va_arg(ap, const char*))) { + while ((p = va_arg(ap, const char*))) if (env_match(*f, p)) { found = true; break; } - } va_end(ap); @@ -555,7 +552,7 @@ char* strv_env_get_n(char * const *l, const char *name, size_t k, ReplaceEnvFlag return NULL; } -char *strv_env_pairs_get(char **l, const char *name) { +char* strv_env_pairs_get(char **l, const char *name) { char *result = NULL; assert(name); @@ -567,7 +564,35 @@ char *strv_env_pairs_get(char **l, const char *name) { return result; } -char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { +int strv_env_get_merged(char **l, char ***ret) { + _cleanup_strv_free_ char **v = NULL; + size_t n = 0; + int r; + + assert(ret); + + /* This converts a strv with pairs of environment variable name + value into a strv of name and + * value concatenated with a "=" separator. E.g. + * input : { "NAME", "value", "FOO", "var" } + * output : { "NAME=value", "FOO=var" } */ + + STRV_FOREACH_PAIR(key, value, l) { + char *s; + + s = strjoin(*key, "=", *value); + if (!s) + return -ENOMEM; + + r = strv_consume_with_size(&v, &n, s); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +char** strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { int k = 0; STRV_FOREACH(p, e) { @@ -799,10 +824,10 @@ int replace_env_full( t = v; } - r = strv_extend_strv(&unset_variables, u, /* filter_duplicates= */ true); + r = strv_extend_strv_consume(&unset_variables, TAKE_PTR(u), /* filter_duplicates= */ true); if (r < 0) return r; - r = strv_extend_strv(&bad_variables, b, /* filter_duplicates= */ true); + r = strv_extend_strv_consume(&bad_variables, TAKE_PTR(b), /* filter_duplicates= */ true); if (r < 0) return r; @@ -934,21 +959,21 @@ int replace_env_argv( return r; n[++k] = NULL; - r = strv_extend_strv(&unset_variables, u, /* filter_duplicates= */ true); + r = strv_extend_strv_consume(&unset_variables, TAKE_PTR(u), /* filter_duplicates= */ true); if (r < 0) return r; - r = strv_extend_strv(&bad_variables, b, /*filter_duplicates= */ true); + r = strv_extend_strv_consume(&bad_variables, TAKE_PTR(b), /* filter_duplicates= */ true); if (r < 0) return r; } if (ret_unset_variables) { - strv_uniq(strv_sort(unset_variables)); + strv_sort_uniq(unset_variables); *ret_unset_variables = TAKE_PTR(unset_variables); } if (ret_bad_variables) { - strv_uniq(strv_sort(bad_variables)); + strv_sort_uniq(bad_variables); *ret_bad_variables = TAKE_PTR(bad_variables); } @@ -966,7 +991,7 @@ int getenv_bool(const char *p) { return parse_boolean(e); } -int getenv_bool_secure(const char *p) { +int secure_getenv_bool(const char *p) { const char *e; e = secure_getenv(p); @@ -976,7 +1001,7 @@ int getenv_bool_secure(const char *p) { return parse_boolean(e); } -int getenv_uint64_secure(const char *p, uint64_t *ret) { +int secure_getenv_uint64(const char *p, uint64_t *ret) { const char *e; assert(p); @@ -1030,6 +1055,17 @@ int setenv_systemd_exec_pid(bool update_only) { return 1; } +int setenv_systemd_log_level(void) { + _cleanup_free_ char *val = NULL; + int r; + + r = log_level_to_string_alloc(log_get_max_level(), &val); + if (r < 0) + return r; + + return RET_NERRNO(setenv("SYSTEMD_LOG_LEVEL", val, /* overwrite= */ true)); +} + int getenv_path_list(const char *name, char ***ret_paths) { _cleanup_strv_free_ char **l = NULL; const char *e; @@ -1132,13 +1168,16 @@ int setenvf(const char *name, bool overwrite, const char *valuef, ...) { return RET_NERRNO(unsetenv(name)); va_start(ap, valuef); - DISABLE_WARNING_FORMAT_NONLITERAL; r = vasprintf(&value, valuef, ap); - REENABLE_WARNING; va_end(ap); if (r < 0) return -ENOMEM; + /* Try to suppress writes if the value is already set correctly (simply because memory management of + * environment variables sucks a bit. */ + if (streq_ptr(getenv(name), value)) + return 0; + return RET_NERRNO(setenv(name, value, overwrite)); } diff --git a/src/libnm-systemd-shared/src/basic/env-util.h b/src/libnm-systemd-shared/src/basic/env-util.h index ad127de39f..203ed65bd1 100644 --- a/src/libnm-systemd-shared/src/basic/env-util.h +++ b/src/libnm-systemd-shared/src/basic/env-util.h @@ -34,17 +34,18 @@ int replace_env_argv(char **argv, char **env, char ***ret, char ***ret_unset_var bool strv_env_is_valid(char **e); #define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL) -char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); +char** strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); bool strv_env_name_is_valid(char **l); bool strv_env_name_or_assignment_is_valid(char **l); char** _strv_env_merge(char **first, ...); #define strv_env_merge(first, ...) _strv_env_merge(first, __VA_ARGS__, POINTER_MAX) -char **strv_env_delete(char **x, size_t n_lists, ...); /* New copy */ +char** strv_env_delete(char **x, size_t n_lists, ...); /* New copy */ -char **strv_env_unset(char **l, const char *p); /* In place ... */ -char **strv_env_unset_many(char **l, ...) _sentinel_; +char** strv_env_unset(char **l, const char *p); /* In place ... */ +char** strv_env_unset_many_internal(char **l, ...) _sentinel_; +#define strv_env_unset_many(l, ...) strv_env_unset_many_internal(l, __VA_ARGS__, NULL) int strv_env_replace_consume(char ***l, char *p); /* In place ... */ int strv_env_replace_strdup(char ***l, const char *assignment); int strv_env_replace_strdup_passthrough(char ***l, const char *assignment); @@ -58,12 +59,13 @@ static inline char* strv_env_get(char * const *x, const char *n) { return strv_env_get_n(x, n, SIZE_MAX, 0); } -char *strv_env_pairs_get(char **l, const char *name) _pure_; +char* strv_env_pairs_get(char **l, const char *name) _pure_; +int strv_env_get_merged(char **l, char ***ret); int getenv_bool(const char *p); -int getenv_bool_secure(const char *p); +int secure_getenv_bool(const char *p); -int getenv_uint64_secure(const char *p, uint64_t *ret); +int secure_getenv_uint64(const char *p, uint64_t *ret); /* Like setenv, but calls unsetenv if value == NULL. */ int set_unset_env(const char *name, const char *value, bool overwrite); @@ -72,6 +74,7 @@ int set_unset_env(const char *name, const char *value, bool overwrite); int putenv_dup(const char *assignment, bool override); int setenv_systemd_exec_pid(bool update_only); +int setenv_systemd_log_level(void); /* Parses and does sanity checks on an environment variable containing * PATH-like colon-separated absolute paths */ diff --git a/src/libnm-systemd-shared/src/basic/errno-util.h b/src/libnm-systemd-shared/src/basic/errno-util.h index 27804e6382..02572e3bdc 100644 --- a/src/libnm-systemd-shared/src/basic/errno-util.h +++ b/src/libnm-systemd-shared/src/basic/errno-util.h @@ -158,7 +158,7 @@ static inline bool ERRNO_IS_NEG_RESOURCE(intmax_t r) { } _DEFINE_ABS_WRAPPER(RESOURCE); -/* Seven different errors for "operation/system call/ioctl/socket feature not supported" */ +/* Seven different errors for "operation/system call/socket feature not supported" */ static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(intmax_t r) { return IN_SET(r, -EOPNOTSUPP, @@ -167,10 +167,17 @@ static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(intmax_t r) { -EAFNOSUPPORT, -EPFNOSUPPORT, -EPROTONOSUPPORT, - -ESOCKTNOSUPPORT); + -ESOCKTNOSUPPORT, + -ENOPROTOOPT); } _DEFINE_ABS_WRAPPER(NOT_SUPPORTED); +/* ioctl() with unsupported command/arg might additionally return EINVAL */ +static inline bool ERRNO_IS_NEG_IOCTL_NOT_SUPPORTED(intmax_t r) { + return ERRNO_IS_NEG_NOT_SUPPORTED(r) || r == -EINVAL; +} +_DEFINE_ABS_WRAPPER(IOCTL_NOT_SUPPORTED); + /* Two different errors for access problems */ static inline bool ERRNO_IS_NEG_PRIVILEGE(intmax_t r) { return IN_SET(r, diff --git a/src/libnm-systemd-shared/src/basic/escape.c b/src/libnm-systemd-shared/src/basic/escape.c index d95f35e798..e50ae68cc6 100644 --- a/src/libnm-systemd-shared/src/basic/escape.c +++ b/src/libnm-systemd-shared/src/basic/escape.c @@ -365,6 +365,8 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape char *ans, *t, *prev, *prev2; const char *f; + assert(s); + /* Escapes all chars in bad, in addition to \ and all special chars, in \xFF style escaping. May be * reversed with cunescape(). If XESCAPE_8_BIT is specified, characters >= 127 are let through * unchanged. This corresponds to non-ASCII printable characters in pre-unicode encodings. @@ -397,7 +399,7 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape if ((unsigned char) *f < ' ' || (!FLAGS_SET(flags, XESCAPE_8_BIT) && (unsigned char) *f >= 127) || - *f == '\\' || strchr(bad, *f)) { + *f == '\\' || (bad && strchr(bad, *f))) { if ((size_t) (t - ans) + 4 + 3 * force_ellipsis > console_width) break; @@ -437,7 +439,7 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags) { if (FLAGS_SET(flags, XESCAPE_8_BIT)) - return xescape_full(str, "", console_width, flags); + return xescape_full(str, /* bad= */ NULL, console_width, flags); else return utf8_escape_non_printable_full(str, console_width, @@ -451,6 +453,12 @@ char* octescape(const char *s, size_t len) { assert(s || len == 0); + if (len == SIZE_MAX) + len = strlen(s); + + if (len > (SIZE_MAX - 1) / 4) + return NULL; + t = buf = new(char, len * 4 + 1); if (!buf) return NULL; diff --git a/src/libnm-systemd-shared/src/basic/ether-addr-util.c b/src/libnm-systemd-shared/src/basic/ether-addr-util.c index 4bf91f690f..11336572cd 100644 --- a/src/libnm-systemd-shared/src/basic/ether-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.c @@ -11,7 +11,7 @@ #include "macro.h" #include "string-util.h" -char *hw_addr_to_string_full( +char* hw_addr_to_string_full( const struct hw_addr_data *addr, HardwareAddressToStringFlags flags, char buffer[static HW_ADDR_TO_STRING_MAX]) { diff --git a/src/libnm-systemd-shared/src/basic/ether-addr-util.h b/src/libnm-systemd-shared/src/basic/ether-addr-util.h index 187e4ef583..8ebf9c031d 100644 --- a/src/libnm-systemd-shared/src/basic/ether-addr-util.h +++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.h @@ -36,11 +36,11 @@ typedef enum HardwareAddressToStringFlags { } HardwareAddressToStringFlags; #define HW_ADDR_TO_STRING_MAX (3*HW_ADDR_MAX_SIZE) -char *hw_addr_to_string_full( +char* hw_addr_to_string_full( const struct hw_addr_data *addr, HardwareAddressToStringFlags flags, char buffer[static HW_ADDR_TO_STRING_MAX]); -static inline char *hw_addr_to_string(const struct hw_addr_data *addr, char buffer[static HW_ADDR_TO_STRING_MAX]) { +static inline char* hw_addr_to_string(const struct hw_addr_data *addr, char buffer[static HW_ADDR_TO_STRING_MAX]) { return hw_addr_to_string_full(addr, 0, buffer); } diff --git a/src/libnm-systemd-shared/src/basic/extract-word.c b/src/libnm-systemd-shared/src/basic/extract-word.c index 160f771b22..012cee6e75 100644 --- a/src/libnm-systemd-shared/src/basic/extract-word.c +++ b/src/libnm-systemd-shared/src/basic/extract-word.c @@ -244,52 +244,43 @@ int extract_first_word_and_warn( * Let's make sure that ExtractFlags fits into an unsigned int. */ assert_cc(sizeof(enum ExtractFlags) <= sizeof(unsigned)); -int extract_many_words(const char **p, const char *separators, unsigned flags, ...) { +int extract_many_words_internal(const char **p, const char *separators, unsigned flags, ...) { va_list ap; - char **l; - int n = 0, i, c, r; + unsigned n = 0; + int r; - /* Parses a number of words from a string, stripping any - * quotes if necessary. */ + /* Parses a number of words from a string, stripping any quotes if necessary. */ assert(p); /* Count how many words are expected */ va_start(ap, flags); - for (;;) { - if (!va_arg(ap, char **)) - break; + while (va_arg(ap, char**)) n++; - } va_end(ap); - if (n <= 0) + if (n == 0) return 0; /* Read all words into a temporary array */ - l = newa0(char*, n); - for (c = 0; c < n; c++) { + char **l = newa0(char*, n); + unsigned c; + for (c = 0; c < n; c++) { r = extract_first_word(p, &l[c], separators, flags); if (r < 0) { free_many_charp(l, c); return r; } - if (r == 0) break; } - /* If we managed to parse all words, return them in the passed - * in parameters */ + /* If we managed to parse all words, return them in the passed in parameters */ va_start(ap, flags); - for (i = 0; i < n; i++) { - char **v; - - v = va_arg(ap, char **); - assert(v); - - *v = l[i]; + FOREACH_ARRAY(i, l, n) { + char **v = ASSERT_PTR(va_arg(ap, char**)); + *v = *i; } va_end(ap); diff --git a/src/libnm-systemd-shared/src/basic/extract-word.h b/src/libnm-systemd-shared/src/basic/extract-word.h index c82ad761ef..da4f6ae674 100644 --- a/src/libnm-systemd-shared/src/basic/extract-word.h +++ b/src/libnm-systemd-shared/src/basic/extract-word.h @@ -19,4 +19,7 @@ typedef enum ExtractFlags { int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags); int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue); -int extract_many_words(const char **p, const char *separators, unsigned flags, ...) _sentinel_; + +int extract_many_words_internal(const char **p, const char *separators, unsigned flags, ...) _sentinel_; +#define extract_many_words(p, separators, flags, ...) \ + extract_many_words_internal(p, separators, flags, ##__VA_ARGS__, NULL) diff --git a/src/libnm-systemd-shared/src/basic/fd-util.c b/src/libnm-systemd-shared/src/basic/fd-util.c index 38866ebb78..be22d6a04f 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.c +++ b/src/libnm-systemd-shared/src/basic/fd-util.c @@ -167,7 +167,10 @@ int fd_nonblock(int fd, bool nonblock) { if (nflags == flags) return 0; - return RET_NERRNO(fcntl(fd, F_SETFL, nflags)); + if (fcntl(fd, F_SETFL, nflags) < 0) + return -errno; + + return 1; } int stdio_disable_nonblock(void) { @@ -209,9 +212,6 @@ int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec) { continue; RET_GATHER(r, fd_cloexec(*fd, cloexec)); - - if (r >= 0) - r = 1; /* report if we did anything */ } return r; @@ -308,7 +308,7 @@ static int close_all_fds_special_case(const int except[], size_t n_except) { case 0: /* Close everything. Yay! */ - if (close_range(3, -1, 0) >= 0) + if (close_range(3, INT_MAX, 0) >= 0) return 1; if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) { @@ -419,7 +419,7 @@ int close_all_fds(const int except[], size_t n_except) { if (sorted[n_sorted-1] >= INT_MAX) /* Dont let the addition below overflow */ return 0; - if (close_range(sorted[n_sorted-1] + 1, -1, 0) >= 0) + if (close_range(sorted[n_sorted-1] + 1, INT_MAX, 0) >= 0) return 0; if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) @@ -464,6 +464,63 @@ int close_all_fds(const int except[], size_t n_except) { return r; } +int pack_fds(int fds[], size_t n_fds) { + if (n_fds <= 0) + return 0; + + /* Shifts around the fds in the provided array such that they + * all end up packed next to each-other, in order, starting + * from SD_LISTEN_FDS_START. This must be called after close_all_fds(); + * it is likely to freeze up otherwise. You should probably use safe_fork_full + * with FORK_CLOSE_ALL_FDS|FORK_PACK_FDS set, to ensure that this is done correctly. + * The fds array is modified in place with the new FD numbers. */ + + assert(fds); + + for (int start = 0;;) { + int restart_from = -1; + + for (int i = start; i < (int) n_fds; i++) { + int nfd; + + /* Already at right index? */ + if (fds[i] == i + 3) + continue; + + nfd = fcntl(fds[i], F_DUPFD, i + 3); + if (nfd < 0) + return -errno; + + safe_close(fds[i]); + fds[i] = nfd; + + /* Hmm, the fd we wanted isn't free? Then + * let's remember that and try again from here */ + if (nfd != i + 3 && restart_from < 0) + restart_from = i; + } + + if (restart_from < 0) + break; + + start = restart_from; + } + + assert(fds[0] == 3); + + return 0; +} + +int fd_validate(int fd) { + if (fd < 0) + return -EBADF; + + if (fcntl(fd, F_GETFD) < 0) + return -errno; + + return 0; +} + int same_fd(int a, int b) { struct stat sta, stb; pid_t pid; @@ -473,25 +530,57 @@ int same_fd(int a, int b) { assert(b >= 0); /* Compares two file descriptors. Note that semantics are quite different depending on whether we - * have kcmp() or we don't. If we have kcmp() this will only return true for dup()ed file - * descriptors, but not otherwise. If we don't have kcmp() this will also return true for two fds of - * the same file, created by separate open() calls. Since we use this call mostly for filtering out - * duplicates in the fd store this difference hopefully doesn't matter too much. */ + * have F_DUPFD_QUERY/kcmp() or we don't. If we have F_DUPFD_QUERY/kcmp() this will only return true + * for dup()ed file descriptors, but not otherwise. If we don't have F_DUPFD_QUERY/kcmp() this will + * also return true for two fds of the same file, created by separate open() calls. Since we use this + * call mostly for filtering out duplicates in the fd store this difference hopefully doesn't matter + * too much. + * + * Guarantees that if either of the passed fds is not allocated we'll return -EBADF. */ + + if (a == b) { + /* Let's validate that the fd is valid */ + r = fd_validate(a); + if (r < 0) + return r; - if (a == b) return true; + } + + /* Try to use F_DUPFD_QUERY if we have it first, as it is the nicest API */ + r = fcntl(a, F_DUPFD_QUERY, b); + if (r > 0) + return true; + if (r == 0) { + /* The kernel will return 0 in case the first fd is allocated, but the 2nd is not. (Which is different in the kcmp() case) Explicitly validate it hence. */ + r = fd_validate(b); + if (r < 0) + return r; + + return false; + } + /* On old kernels (< 6.10) that do not support F_DUPFD_QUERY this will return EINVAL for regular fds, and EBADF on O_PATH fds. Confusing. */ + if (errno == EBADF) { + /* EBADF could mean two things: the first fd is not valid, or it is valid and is O_PATH and + * F_DUPFD_QUERY is not supported. Let's validate the fd explicitly, to distinguish this + * case. */ + r = fd_validate(a); + if (r < 0) + return r; + + /* If the fd is valid, but we got EBADF, then let's try kcmp(). */ + } else if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno) && errno != EINVAL) + return -errno; /* Try to use kcmp() if we have it. */ pid = getpid_cached(); r = kcmp(pid, pid, KCMP_FILE, a, b); - if (r == 0) - return true; - if (r > 0) - return false; + if (r >= 0) + return !r; if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) return -errno; - /* We don't have kcmp(), use fstat() instead. */ + /* We have neither F_DUPFD_QUERY nor kcmp(), use fstat() instead. */ if (fstat(a, &sta) < 0) return -errno; @@ -521,14 +610,21 @@ int same_fd(int a, int b) { } void cmsg_close_all(struct msghdr *mh) { - struct cmsghdr *cmsg; - assert(mh); - CMSG_FOREACH(cmsg, mh) - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) + struct cmsghdr *cmsg; + CMSG_FOREACH(cmsg, mh) { + if (cmsg->cmsg_level != SOL_SOCKET) + continue; + + if (cmsg->cmsg_type == SCM_RIGHTS) close_many(CMSG_TYPED_DATA(cmsg, int), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int)); + else if (cmsg->cmsg_type == SCM_PIDFD) { + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(int))); + safe_close(*CMSG_TYPED_DATA(cmsg, int)); + } + } } bool fdname_is_valid(const char *s) { @@ -567,16 +663,8 @@ int fd_get_path(int fd, char **ret) { return safe_getcwd(ret); r = readlink_malloc(FORMAT_PROC_FD_PATH(fd), ret); - if (r == -ENOENT) { - /* ENOENT can mean two things: that the fd does not exist or that /proc is not mounted. Let's make - * things debuggable and distinguish the two. */ - - if (proc_mounted() == 0) - return -ENOSYS; /* /proc is not available or not set up properly, we're most likely in some chroot - * environment. */ - return -EBADF; /* The directory exists, hence it's the fd that doesn't. */ - } - + if (r == -ENOENT) + return proc_fd_enoent_errno(); return r; } @@ -714,8 +802,7 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_ } /* Let's assemble fd[] with the fds to install in place of stdin/stdout/stderr */ - for (int i = 0; i < 3; i++) { - + for (int i = 0; i < 3; i++) if (fd[i] < 0) fd[i] = null_fd; /* A negative parameter means: connect this one to /dev/null */ else if (fd[i] != i && fd[i] < 3) { @@ -728,20 +815,16 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_ fd[i] = copy_fd[i]; } - } /* At this point we now have the fds to use in fd[], and they are all above the stdio range, so that * we have freedom to move them around. If the fds already were at the right places then the specific * fds are -EBADF. Let's now move them to the right places. This is the point of no return. */ - for (int i = 0; i < 3; i++) { - + for (int i = 0; i < 3; i++) if (fd[i] == i) { - /* fd is already in place, but let's make sure O_CLOEXEC is off */ r = fd_cloexec(i, false); if (r < 0) goto finish; - } else { assert(fd[i] > 2); @@ -750,7 +833,6 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_ goto finish; } } - } r = 0; @@ -773,8 +855,6 @@ finish: } int fd_reopen(int fd, int flags) { - int r; - assert(fd >= 0 || fd == AT_FDCWD); assert(!FLAGS_SET(flags, O_CREAT)); @@ -810,13 +890,47 @@ int fd_reopen(int fd, int flags) { if (errno != ENOENT) return -errno; - r = proc_mounted(); - if (r == 0) - return -ENOSYS; /* if we have no /proc/, the concept is not implementable */ + return proc_fd_enoent_errno(); + } - return r > 0 ? -EBADF : -ENOENT; /* If /proc/ is definitely around then this means the fd is - * not valid, otherwise let's propagate the original - * error */ + return new_fd; +} + +int fd_reopen_propagate_append_and_position(int fd, int flags) { + /* Invokes fd_reopen(fd, flags), but propagates O_APPEND if set on original fd, and also tries to + * keep current file position. + * + * You should use this if the original fd potentially is O_APPEND, otherwise we get rather + * "unexpected" behavior. Unless you intentionally want to overwrite pre-existing data, and have + * your output overwritten by the next user. + * + * Use case: "systemd-run --pty >> some-log". + * + * The "keep position" part is obviously nonsense for the O_APPEND case, but should reduce surprises + * if someone carefully pre-positioned the passed in original input or non-append output FDs. */ + + assert(fd >= 0); + assert(!(flags & (O_APPEND|O_DIRECTORY))); + + int existing_flags = fcntl(fd, F_GETFL); + if (existing_flags < 0) + return -errno; + + int new_fd = fd_reopen(fd, flags | (existing_flags & O_APPEND)); + if (new_fd < 0) + return new_fd; + + /* Try to adjust the offset, but ignore errors. */ + off_t p = lseek(fd, 0, SEEK_CUR); + if (p > 0) { + off_t new_p = lseek(new_fd, p, SEEK_SET); + if (new_p < 0) + log_debug_errno(errno, + "Failed to propagate file position for re-opened fd %d, ignoring: %m", + fd); + else if (new_p != p) + log_debug("Failed to propagate file position for re-opened fd %d (%lld != %lld), ignoring.", + fd, (long long) new_p, (long long) p); } return new_fd; @@ -866,6 +980,38 @@ int fd_is_opath(int fd) { return FLAGS_SET(r, O_PATH); } +int fd_verify_safe_flags_full(int fd, int extra_flags) { + int flags, unexpected_flags; + + /* Check if an extrinsic fd is safe to work on (by a privileged service). This ensures that clients + * can't trick a privileged service into giving access to a file the client doesn't already have + * access to (especially via something like O_PATH). + * + * O_NOFOLLOW: For some reason the kernel will return this flag from fcntl(); it doesn't go away + * immediately after open(). It should have no effect whatsoever to an already-opened FD, + * and since we refuse O_PATH it should be safe. + * + * RAW_O_LARGEFILE: glibc secretly sets this and neglects to hide it from us if we call fcntl. + * See comment in missing_fcntl.h for more details about this. + * + * If 'extra_flags' is specified as non-zero the included flags are also allowed. + */ + + assert(fd >= 0); + + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; + + unexpected_flags = flags & ~(O_ACCMODE|O_NOFOLLOW|RAW_O_LARGEFILE|extra_flags); + if (unexpected_flags != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EREMOTEIO), + "Unexpected flags set for extrinsic fd: 0%o", + (unsigned) unexpected_flags); + + return flags & (O_ACCMODE | extra_flags); /* return the flags variable, but remove the noise */ +} + int read_nr_open(void) { _cleanup_free_ char *nr_open = NULL; int r; @@ -967,8 +1113,6 @@ int fds_are_same_mount(int fd1, int fd2) { int mntid; r = path_get_mnt_id_at_fallback(fd1, "", &mntid); - if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) - return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); @@ -981,8 +1125,6 @@ int fds_are_same_mount(int fd1, int fd2) { int mntid; r = path_get_mnt_id_at_fallback(fd2, "", &mntid); - if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) - return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); @@ -994,7 +1136,7 @@ int fds_are_same_mount(int fd1, int fd2) { return statx_mount_same(&st1.nsx, &st2.nsx); } -const char *accmode_to_string(int flags) { +const char* accmode_to_string(int flags) { switch (flags & O_ACCMODE) { case O_RDONLY: return "ro"; @@ -1007,10 +1149,27 @@ const char *accmode_to_string(int flags) { } } -char *format_proc_pid_fd_path(char buf[static PROC_PID_FD_PATH_MAX], pid_t pid, int fd) { +char* format_proc_pid_fd_path(char buf[static PROC_PID_FD_PATH_MAX], pid_t pid, int fd) { assert(buf); assert(fd >= 0); assert(pid >= 0); assert_se(snprintf_ok(buf, PROC_PID_FD_PATH_MAX, "/proc/" PID_FMT "/fd/%i", pid == 0 ? getpid_cached() : pid, fd)); return buf; } + +int proc_fd_enoent_errno(void) { + int r; + + /* When ENOENT is returned during the use of FORMAT_PROC_FD_PATH, it can mean two things: + * that the fd does not exist or that /proc/ is not mounted. + * Let's make things debuggable and figure out the most appropriate errno. */ + + r = proc_mounted(); + if (r == 0) + return -ENOSYS; /* /proc/ is not available or not set up properly, we're most likely + in some chroot environment. */ + if (r > 0) + return -EBADF; /* If /proc/ is definitely around then this means the fd is not valid. */ + + return -ENOENT; /* Otherwise let's propagate the original ENOENT. */ +} diff --git a/src/libnm-systemd-shared/src/basic/fd-util.h b/src/libnm-systemd-shared/src/basic/fd-util.h index 183266513a..93b254c680 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.h +++ b/src/libnm-systemd-shared/src/basic/fd-util.h @@ -8,6 +8,7 @@ #include #include "macro.h" +#include "missing_fcntl.h" #include "stdio-util.h" /* maximum length of fdname */ @@ -77,6 +78,9 @@ int get_max_fd(void); int close_all_fds(const int except[], size_t n_except); int close_all_fds_without_malloc(const int except[], size_t n_except); +int pack_fds(int fds[], size_t n); + +int fd_validate(int fd); int same_fd(int a, int b); void cmsg_close_all(struct msghdr *mh); @@ -108,8 +112,16 @@ static inline int make_null_stdio(void) { }) int fd_reopen(int fd, int flags); +int fd_reopen_propagate_append_and_position(int fd, int flags); int fd_reopen_condition(int fd, int flags, int mask, int *ret_new_fd); + int fd_is_opath(int fd); + +int fd_verify_safe_flags_full(int fd, int extra_flags); +static inline int fd_verify_safe_flags(int fd) { + return fd_verify_safe_flags_full(fd, 0); +} + int read_nr_open(void); int fd_get_diskseq(int fd, uint64_t *ret); @@ -130,7 +142,7 @@ int fds_are_same_mount(int fd1, int fd2); #define PROC_FD_PATH_MAX \ (STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)) -static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int fd) { +static inline char* format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int fd) { assert(buf); assert(fd >= 0); assert_se(snprintf_ok(buf, PROC_FD_PATH_MAX, "/proc/self/fd/%i", fd)); @@ -144,13 +156,15 @@ static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int f #define PROC_PID_FD_PATH_MAX \ (STRLEN("/proc//fd/") + DECIMAL_STR_MAX(pid_t) + DECIMAL_STR_MAX(int)) -char *format_proc_pid_fd_path(char buf[static PROC_PID_FD_PATH_MAX], pid_t pid, int fd); +char* format_proc_pid_fd_path(char buf[static PROC_PID_FD_PATH_MAX], pid_t pid, int fd); /* Kinda the same as FORMAT_PROC_FD_PATH(), but goes by PID rather than "self" symlink */ #define FORMAT_PROC_PID_FD_PATH(pid, fd) \ format_proc_pid_fd_path((char[PROC_PID_FD_PATH_MAX]) {}, (pid), (fd)) -const char *accmode_to_string(int flags); +int proc_fd_enoent_errno(void); + +const char* accmode_to_string(int flags); /* Like ASSERT_PTR, but for fds */ #define ASSERT_FD(fd) \ diff --git a/src/libnm-systemd-shared/src/basic/fileio.c b/src/libnm-systemd-shared/src/basic/fileio.c index 001c19378e..9f2bbc1323 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.c +++ b/src/libnm-systemd-shared/src/basic/fileio.c @@ -14,10 +14,12 @@ #include "alloc-util.h" #include "chase.h" +#include "extract-word.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "hexdecoct.h" +#include "label.h" #include "log.h" #include "macro.h" #include "mkdir.h" @@ -32,7 +34,7 @@ #include "tmpfile-util.h" /* The maximum size of the file we'll read in one go in read_full_file() (64M). */ -#define READ_FULL_BYTES_MAX (64U*1024U*1024U - 1U) +#define READ_FULL_BYTES_MAX (64U * U64_MB - UINT64_C(1)) /* Used when a size is specified for read_full_file() with READ_FULL_FILE_UNBASE64 or _UNHEX */ #define READ_FULL_FILE_ENCODED_STRING_AMPLIFICATION_BOUNDARY 3 @@ -45,7 +47,7 @@ * exponentially in a loop. We use a size limit of 4M-2 because 4M-1 is the maximum buffer that /proc/sys/ * allows us to read() (larger reads will fail with ENOMEM), and we want to read one extra byte so that we * can detect EOFs. */ -#define READ_VIRTUAL_BYTES_MAX (4U*1024U*1024U - 2U) +#define READ_VIRTUAL_BYTES_MAX (4U * U64_MB - UINT64_C(2)) int fdopen_unlocked(int fd, const char *options, FILE **ret) { assert(ret); @@ -118,7 +120,7 @@ FILE* fmemopen_unlocked(void *buf, size_t size, const char *mode) { return f; } -int write_string_stream_ts( +int write_string_stream_full( FILE *f, const char *line, WriteStringFileFlags flags, @@ -230,23 +232,41 @@ static int write_string_file_atomic_at( /* Note that we'd really like to use O_TMPFILE here, but can't really, since we want replacement * semantics here, and O_TMPFILE can't offer that. i.e. rename() replaces but linkat() doesn't. */ + mode_t mode = write_string_file_flags_to_mode(flags); + + bool call_label_ops_post = false; + if (FLAGS_SET(flags, WRITE_STRING_FILE_LABEL)) { + r = label_ops_pre(dir_fd, fn, mode); + if (r < 0) + return r; + + call_label_ops_post = true; + } + r = fopen_temporary_at(dir_fd, fn, &f, &p); - if (r < 0) - return r; - - r = write_string_stream_ts(f, line, flags, ts); if (r < 0) goto fail; - r = fchmod_umask(fileno(f), write_string_file_flags_to_mode(flags)); - if (r < 0) - goto fail; + if (call_label_ops_post) { + call_label_ops_post = false; - if (renameat(dir_fd, p, dir_fd, fn) < 0) { - r = -errno; - goto fail; + r = label_ops_post(fileno(f), /* path= */ NULL, /* created= */ true); + if (r < 0) + goto fail; } + r = write_string_stream_full(f, line, flags, ts); + if (r < 0) + goto fail; + + r = fchmod_umask(fileno(f), mode); + if (r < 0) + goto fail; + + r = RET_NERRNO(renameat(dir_fd, p, dir_fd, fn)); + if (r < 0) + goto fail; + if (FLAGS_SET(flags, WRITE_STRING_FILE_SYNC)) { /* Sync the rename, too */ r = fsync_directory_of_file(fileno(f)); @@ -257,20 +277,26 @@ static int write_string_file_atomic_at( return 0; fail: - (void) unlinkat(dir_fd, p, 0); + if (call_label_ops_post) + (void) label_ops_post(f ? fileno(f) : dir_fd, f ? NULL : fn, /* created= */ !!f); + + if (f) + (void) unlinkat(dir_fd, p, 0); return r; } -int write_string_file_ts_at( +int write_string_file_full( int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags, - const struct timespec *ts) { + const struct timespec *ts, + const char *label_fn) { + bool call_label_ops_post = false, made_file = false; _cleanup_fclose_ FILE *f = NULL; _cleanup_close_ int fd = -EBADF; - int q, r; + int r; assert(fn); assert(line); @@ -292,21 +318,40 @@ int write_string_file_ts_at( goto fail; return r; - } else - assert(!ts); + } + + mode_t mode = write_string_file_flags_to_mode(flags); + + if (FLAGS_SET(flags, WRITE_STRING_FILE_LABEL|WRITE_STRING_FILE_CREATE)) { + r = label_ops_pre(dir_fd, label_fn ?: fn, mode); + if (r < 0) + goto fail; + + call_label_ops_post = true; + } /* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */ - fd = openat(dir_fd, fn, O_CLOEXEC|O_NOCTTY | - (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) | - (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) | - (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) | - (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY), - write_string_file_flags_to_mode(flags)); + fd = openat_report_new( + dir_fd, fn, O_CLOEXEC | O_NOCTTY | + (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) | + (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) | + (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) | + (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY), + mode, + &made_file); if (fd < 0) { - r = -errno; + r = fd; goto fail; } + if (call_label_ops_post) { + call_label_ops_post = false; + + r = label_ops_post(fd, /* path= */ NULL, made_file); + if (r < 0) + goto fail; + } + r = take_fdopen_unlocked(&fd, "w", &f); if (r < 0) goto fail; @@ -314,26 +359,31 @@ int write_string_file_ts_at( if (flags & WRITE_STRING_FILE_DISABLE_BUFFER) setvbuf(f, NULL, _IONBF, 0); - r = write_string_stream_ts(f, line, flags, ts); + r = write_string_stream_full(f, line, flags, ts); if (r < 0) goto fail; return 0; fail: + if (call_label_ops_post) + (void) label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : fn, made_file); + + if (made_file) + (void) unlinkat(dir_fd, fn, 0); + if (!(flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE)) return r; f = safe_fclose(f); + fd = safe_close(fd); - /* OK, the operation failed, but let's see if the right - * contents in place already. If so, eat up the error. */ + /* OK, the operation failed, but let's see if the right contents in place already. If so, eat up the + * error. */ + if (verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) || (flags & WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE)) > 0) + return 0; - q = verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) || (flags & WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE)); - if (q <= 0) - return r; - - return 0; + return r; } int write_string_filef( @@ -355,6 +405,22 @@ int write_string_filef( return write_string_file(fn, p, flags); } +int write_base64_file_at( + int dir_fd, + const char *fn, + const struct iovec *data, + WriteStringFileFlags flags) { + + _cleanup_free_ char *encoded = NULL; + ssize_t n; + + n = base64mem_full(data ? data->iov_base : NULL, data ? data->iov_len : 0, 79, &encoded); + if (n < 0) + return n; + + return write_string_file_at(dir_fd, fn, encoded, flags); +} + int read_one_line_file_at(int dir_fd, const char *filename, char **ret) { _cleanup_fclose_ FILE *f = NULL; int r; @@ -791,35 +857,49 @@ int read_full_file_full( return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size); } -int executable_is_script(const char *path, char **interpreter) { - _cleanup_free_ char *line = NULL; - size_t len; - char *ans; +int script_get_shebang_interpreter(const char *path, char **ret) { + _cleanup_fclose_ FILE *f = NULL; int r; assert(path); - r = read_one_line_file(path, &line); - if (r == -ENOBUFS) /* First line overly long? if so, then it's not a script */ - return 0; + f = fopen(path, "re"); + if (!f) + return -errno; + + char c; + r = safe_fgetc(f, &c); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + if (c != '#') + return -EMEDIUMTYPE; + r = safe_fgetc(f, &c); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + if (c != '!') + return -EMEDIUMTYPE; + + _cleanup_free_ char *line = NULL; + r = read_line(f, LONG_LINE_MAX, &line); if (r < 0) return r; - if (!startswith(line, "#!")) - return 0; + _cleanup_free_ char *p = NULL; + const char *s = line; - ans = strstrip(line + 2); - len = strcspn(ans, " \t"); + r = extract_first_word(&s, &p, /* separators = */ NULL, /* flags = */ 0); + if (r < 0) + return r; + if (r == 0) + return -ENOEXEC; - if (len == 0) - return 0; - - ans = strndup(ans, len); - if (!ans) - return -ENOMEM; - - *interpreter = ans; - return 1; + if (ret) + *ret = TAKE_PTR(p); + return 0; } /** @@ -892,19 +972,21 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin return 0; } -DIR *xopendirat(int fd, const char *name, int flags) { - _cleanup_close_ int nfd = -EBADF; +DIR* xopendirat(int dir_fd, const char *name, int flags) { + _cleanup_close_ int fd = -EBADF; - assert(!(flags & O_CREAT)); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(name); + assert(!(flags & (O_CREAT|O_TMPFILE))); - if (fd == AT_FDCWD && flags == 0) + if (dir_fd == AT_FDCWD && flags == 0) return opendir(name); - nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0); - if (nfd < 0) + fd = openat(dir_fd, name, O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags); + if (fd < 0) return NULL; - return take_fdopendir(&nfd); + return take_fdopendir(&fd); } int fopen_mode_to_flags(const char *mode) { @@ -1354,6 +1436,29 @@ int fputs_with_separator(FILE *f, const char *s, const char *separator, bool *sp return 0; } +int fputs_with_newline(FILE *f, const char *s) { + + /* This is like fputs() but outputs a trailing newline char, but only if the string isn't empty + * and doesn't end in a newline already. Returns 0 in case we didn't append a newline, > 0 otherwise. */ + + if (isempty(s)) + return 0; + + if (!f) + f = stdout; + + if (fputs(s, f) < 0) + return -EIO; + + if (endswith(s, "\n")) + return 0; + + if (fputc('\n', f) < 0) + return -EIO; + + return 1; +} + /* A bitmask of the EOL markers we know */ typedef enum EndOfLineMarker { EOL_NONE = 0, @@ -1504,7 +1609,7 @@ int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) { int read_stripped_line(FILE *f, size_t limit, char **ret) { _cleanup_free_ char *s = NULL; - int r; + int r, k; assert(f); @@ -1513,23 +1618,17 @@ int read_stripped_line(FILE *f, size_t limit, char **ret) { return r; if (ret) { - const char *p; - - p = strstrip(s); + const char *p = strstrip(s); if (p == s) *ret = TAKE_PTR(s); else { - char *copy; - - copy = strdup(p); - if (!copy) - return -ENOMEM; - - *ret = copy; + k = strdup_to(ret, p); + if (k < 0) + return k; } } - return r; + return r > 0; /* Return 1 if something was read. */ } int safe_fgetc(FILE *f, char *ret) { diff --git a/src/libnm-systemd-shared/src/basic/fileio.h b/src/libnm-systemd-shared/src/basic/fileio.h index 03c3f3ff28..bd053050e1 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.h +++ b/src/libnm-systemd-shared/src/basic/fileio.h @@ -28,11 +28,7 @@ typedef enum { WRITE_STRING_FILE_MODE_0600 = 1 << 10, WRITE_STRING_FILE_MODE_0444 = 1 << 11, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 12, - - /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one - more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file() - and friends. */ - + WRITE_STRING_FILE_LABEL = 1 << 13, } WriteStringFileFlags; typedef enum { @@ -51,23 +47,22 @@ DIR* take_fdopendir(int *dfd); FILE* open_memstream_unlocked(char **ptr, size_t *sizeloc); FILE* fmemopen_unlocked(void *buf, size_t size, const char *mode); -int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags, const struct timespec *ts); +int write_string_stream_full(FILE *f, const char *line, WriteStringFileFlags flags, const struct timespec *ts); static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) { - return write_string_stream_ts(f, line, flags, NULL); -} -int write_string_file_ts_at(int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts); -static inline int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts) { - return write_string_file_ts_at(AT_FDCWD, fn, line, flags, ts); -} -static inline int write_string_file_at(int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags) { - return write_string_file_ts_at(dir_fd, fn, line, flags, NULL); -} -static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) { - return write_string_file_ts(fn, line, flags, NULL); + return write_string_stream_full(f, line, flags, NULL); } +int write_string_file_full(int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts, const char *label_fn); +static inline int write_string_file_at(int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags) { + return write_string_file_full(dir_fd, fn, line, flags, NULL, NULL); +} +static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) { + return write_string_file_at(AT_FDCWD, fn, line, flags); +} int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4); +int write_base64_file_at(int dir_fd, const char *fn, const struct iovec *data, WriteStringFileFlags flags); + int read_one_line_file_at(int dir_fd, const char *filename, char **ret); static inline int read_one_line_file(const char *filename, char **ret) { return read_one_line_file_at(AT_FDCWD, filename, ret); @@ -99,11 +94,11 @@ static inline int verify_file(const char *fn, const char *blob, bool accept_extr return verify_file_at(AT_FDCWD, fn, blob, accept_extra_nl); } -int executable_is_script(const char *path, char **interpreter); +int script_get_shebang_interpreter(const char *path, char **ret); int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field); -DIR *xopendirat(int dirfd, const char *name, int flags); +DIR* xopendirat(int dir_fd, const char *name, int flags); typedef enum XfopenFlags { XFOPEN_UNLOCKED = 1 << 0, /* call __fsetlocking(FSETLOCKING_BYCALLER) after opened */ @@ -144,6 +139,7 @@ int write_timestamp_file_atomic(const char *fn, usec_t n); int read_timestamp_file(const char *fn, usec_t *ret); int fputs_with_separator(FILE *f, const char *s, const char *separator, bool *space); +int fputs_with_newline(FILE *f, const char *s); typedef enum ReadLineFlags { READ_LINE_ONLY_NUL = 1 << 0, @@ -152,6 +148,14 @@ typedef enum ReadLineFlags { } ReadLineFlags; int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret); +static inline int read_line(FILE *f, size_t limit, char **ret) { + return read_line_full(f, limit, 0, ret); +} +static inline int read_nul_string(FILE *f, size_t limit, char **ret) { + return read_line_full(f, limit, READ_LINE_ONLY_NUL, ret); +} + +int read_stripped_line(FILE *f, size_t limit, char **ret); static inline bool file_offset_beyond_memory_size(off_t x) { if (x < 0) /* off_t is signed, filter that out */ @@ -159,16 +163,6 @@ static inline bool file_offset_beyond_memory_size(off_t x) { return (uint64_t) x > (uint64_t) SIZE_MAX; } -static inline int read_line(FILE *f, size_t limit, char **ret) { - return read_line_full(f, limit, 0, ret); -} - -static inline int read_nul_string(FILE *f, size_t limit, char **ret) { - return read_line_full(f, limit, READ_LINE_ONLY_NUL, ret); -} - -int read_stripped_line(FILE *f, size_t limit, char **ret); - int safe_fgetc(FILE *f, char *ret); int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line); diff --git a/src/libnm-systemd-shared/src/basic/format-ifname.c b/src/libnm-systemd-shared/src/basic/format-ifname.c new file mode 100644 index 0000000000..ce4933c57d --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/format-ifname.c @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "format-ifname.h" +#include "string-util.h" + +assert_cc(STRLEN("%") + DECIMAL_STR_MAX(int) <= IF_NAMESIZE); + +int format_ifname_full(int ifindex, FormatIfnameFlag flag, char buf[static IF_NAMESIZE]) { + if (ifindex <= 0) + return -EINVAL; + + if (if_indextoname(ifindex, buf)) + return 0; + + if (!FLAGS_SET(flag, FORMAT_IFNAME_IFINDEX)) + return -errno; + + if (FLAGS_SET(flag, FORMAT_IFNAME_IFINDEX_WITH_PERCENT)) + assert_se(snprintf_ok(buf, IF_NAMESIZE, "%%%d", ifindex)); + else + assert_se(snprintf_ok(buf, IF_NAMESIZE, "%d", ifindex)); + + return 0; +} + +int format_ifname_full_alloc(int ifindex, FormatIfnameFlag flag, char **ret) { + char buf[IF_NAMESIZE]; + int r; + + assert(ret); + + r = format_ifname_full(ifindex, flag, buf); + if (r < 0) + return r; + + return strdup_to(ret, buf); +} diff --git a/src/libnm-systemd-shared/src/basic/format-ifname.h b/src/libnm-systemd-shared/src/basic/format-ifname.h new file mode 100644 index 0000000000..84415b1388 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/format-ifname.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +typedef enum { + FORMAT_IFNAME_IFINDEX = 1 << 0, + FORMAT_IFNAME_IFINDEX_WITH_PERCENT = (1 << 1) | FORMAT_IFNAME_IFINDEX, +} FormatIfnameFlag; + +int format_ifname_full(int ifindex, FormatIfnameFlag flag, char buf[static IF_NAMESIZE]); +int format_ifname_full_alloc(int ifindex, FormatIfnameFlag flag, char **ret); + +static inline int format_ifname(int ifindex, char buf[static IF_NAMESIZE]) { + return format_ifname_full(ifindex, 0, buf); +} +static inline int format_ifname_alloc(int ifindex, char **ret) { + return format_ifname_full_alloc(ifindex, 0, ret); +} + +static inline char* _format_ifname_full(int ifindex, FormatIfnameFlag flag, char buf[static IF_NAMESIZE]) { + (void) format_ifname_full(ifindex, flag, buf); + return buf; +} + +#define FORMAT_IFNAME_FULL(index, flag) _format_ifname_full(index, flag, (char[IF_NAMESIZE]){}) +#define FORMAT_IFNAME(index) _format_ifname_full(index, 0, (char[IF_NAMESIZE]){}) diff --git a/src/libnm-systemd-shared/src/basic/format-util.c b/src/libnm-systemd-shared/src/basic/format-util.c index 94501853a1..09cb716e7d 100644 --- a/src/libnm-systemd-shared/src/basic/format-util.c +++ b/src/libnm-systemd-shared/src/basic/format-util.c @@ -5,44 +5,7 @@ #include "stdio-util.h" #include "strxcpyx.h" -assert_cc(STRLEN("%") + DECIMAL_STR_MAX(int) <= IF_NAMESIZE); -int format_ifname_full(int ifindex, FormatIfnameFlag flag, char buf[static IF_NAMESIZE]) { - if (ifindex <= 0) - return -EINVAL; - - if (if_indextoname(ifindex, buf)) - return 0; - - if (!FLAGS_SET(flag, FORMAT_IFNAME_IFINDEX)) - return -errno; - - if (FLAGS_SET(flag, FORMAT_IFNAME_IFINDEX_WITH_PERCENT)) - assert(snprintf_ok(buf, IF_NAMESIZE, "%%%d", ifindex)); - else - assert(snprintf_ok(buf, IF_NAMESIZE, "%d", ifindex)); - - return 0; -} - -int format_ifname_full_alloc(int ifindex, FormatIfnameFlag flag, char **ret) { - char buf[IF_NAMESIZE], *copy; - int r; - - assert(ret); - - r = format_ifname_full(ifindex, flag, buf); - if (r < 0) - return r; - - copy = strdup(buf); - if (!copy) - return -ENOMEM; - - *ret = copy; - return 0; -} - -char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) { +char* format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) { typedef struct { const char *suffix; uint64_t factor; @@ -75,15 +38,17 @@ char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) { for (size_t i = 0; i < n; i++) if (t >= table[i].factor) { - if (flag & FORMAT_BYTES_BELOW_POINT) { + uint64_t remainder = i != n - 1 ? + (t / table[i + 1].factor * 10 / table[n - 1].factor) % 10 : + (t * 10 / table[i].factor) % 10; + + if (FLAGS_SET(flag, FORMAT_BYTES_BELOW_POINT) && remainder > 0) (void) snprintf(buf, l, "%" PRIu64 ".%" PRIu64 "%s", t / table[i].factor, - i != n - 1 ? - (t / table[i + 1].factor * UINT64_C(10) / table[n - 1].factor) % UINT64_C(10): - (t * UINT64_C(10) / table[i].factor) % UINT64_C(10), + remainder, table[i].suffix); - } else + else (void) snprintf(buf, l, "%" PRIu64 "%s", t / table[i].factor, diff --git a/src/libnm-systemd-shared/src/basic/format-util.h b/src/libnm-systemd-shared/src/basic/format-util.h index 8719df3e29..b528c005ca 100644 --- a/src/libnm-systemd-shared/src/basic/format-util.h +++ b/src/libnm-systemd-shared/src/basic/format-util.h @@ -2,7 +2,6 @@ #pragma once #include -#include #include #include "cgroup-util.h" @@ -18,6 +17,14 @@ assert_cc(sizeof(uid_t) == sizeof(uint32_t)); assert_cc(sizeof(gid_t) == sizeof(uint32_t)); #define GID_FMT "%" PRIu32 +/* Note: the lifetime of the compound literal is the immediately surrounding block, + * see C11 §6.5.2.5, and + * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */ +#define FORMAT_UID(uid) \ + snprintf_ok((char[DECIMAL_STR_MAX(uid_t)]){}, DECIMAL_STR_MAX(uid_t), UID_FMT, uid) +#define FORMAT_GID(gid) \ + snprintf_ok((char[DECIMAL_STR_MAX(gid_t)]){}, DECIMAL_STR_MAX(gid_t), GID_FMT, gid) + #if SIZEOF_TIME_T == 8 # define PRI_TIME PRIi64 #elif SIZEOF_TIME_T == 4 @@ -58,29 +65,6 @@ assert_cc(sizeof(gid_t) == sizeof(uint32_t)); # error Unknown ino_t size #endif -typedef enum { - FORMAT_IFNAME_IFINDEX = 1 << 0, - FORMAT_IFNAME_IFINDEX_WITH_PERCENT = (1 << 1) | FORMAT_IFNAME_IFINDEX, -} FormatIfnameFlag; - -int format_ifname_full(int ifindex, FormatIfnameFlag flag, char buf[static IF_NAMESIZE]); -int format_ifname_full_alloc(int ifindex, FormatIfnameFlag flag, char **ret); - -static inline int format_ifname(int ifindex, char buf[static IF_NAMESIZE]) { - return format_ifname_full(ifindex, 0, buf); -} -static inline int format_ifname_alloc(int ifindex, char **ret) { - return format_ifname_full_alloc(ifindex, 0, ret); -} - -static inline char *_format_ifname_full(int ifindex, FormatIfnameFlag flag, char buf[static IF_NAMESIZE]) { - (void) format_ifname_full(ifindex, flag, buf); - return buf; -} - -#define FORMAT_IFNAME_FULL(index, flag) _format_ifname_full(index, flag, (char[IF_NAMESIZE]){}) -#define FORMAT_IFNAME(index) _format_ifname_full(index, 0, (char[IF_NAMESIZE]){}) - typedef enum { FORMAT_BYTES_USE_IEC = 1 << 0, FORMAT_BYTES_BELOW_POINT = 1 << 1, @@ -89,10 +73,10 @@ typedef enum { #define FORMAT_BYTES_MAX 16U -char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) _warn_unused_result_; +char* format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) _warn_unused_result_; _warn_unused_result_ -static inline char *format_bytes(char *buf, size_t l, uint64_t t) { +static inline char* format_bytes(char *buf, size_t l, uint64_t t) { return format_bytes_full(buf, l, t, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | FORMAT_BYTES_TRAILING_B); } diff --git a/src/libnm-systemd-shared/src/basic/fs-util.c b/src/libnm-systemd-shared/src/basic/fs-util.c index ee38e0266a..3b3fa1811e 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.c +++ b/src/libnm-systemd-shared/src/basic/fs-util.c @@ -10,6 +10,7 @@ #include "alloc-util.h" #include "btrfs.h" +#include "chattr-util.h" #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" @@ -116,7 +117,11 @@ int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char int readlinkat_malloc(int fd, const char *p, char **ret) { size_t l = PATH_MAX; - assert(p); + assert(fd >= 0 || fd == AT_FDCWD); + + if (fd < 0 && isempty(p)) + return -EISDIR; /* In this case, the fd points to the current working directory, and is + * definitely not a symlink. Let's return earlier. */ for (;;) { _cleanup_free_ char *c = NULL; @@ -126,7 +131,7 @@ int readlinkat_malloc(int fd, const char *p, char **ret) { if (!c) return -ENOMEM; - n = readlinkat(fd, p, c, l); + n = readlinkat(fd, strempty(p), c, l); if (n < 0) return -errno; @@ -148,10 +153,6 @@ int readlinkat_malloc(int fd, const char *p, char **ret) { } } -int readlink_malloc(const char *p, char **ret) { - return readlinkat_malloc(AT_FDCWD, p, ret); -} - int readlink_value(const char *p, char **ret) { _cleanup_free_ char *link = NULL, *name = NULL; int r; @@ -309,10 +310,7 @@ int fchmod_opath(int fd, mode_t m) { if (errno != ENOENT) return -errno; - if (proc_mounted() == 0) - return -ENOSYS; /* if we have no /proc/, the concept is not implementable */ - - return -ENOENT; + return proc_fd_enoent_errno(); } return 0; @@ -321,14 +319,21 @@ int fchmod_opath(int fd, mode_t m) { int futimens_opath(int fd, const struct timespec ts[2]) { /* Similar to fchmod_opath() but for futimens() */ - if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, 0) < 0) { + assert(fd >= 0); + + if (utimensat(fd, "", ts, AT_EMPTY_PATH) >= 0) + return 0; + if (errno != EINVAL) + return -errno; + + /* Support for AT_EMPTY_PATH is added rather late (kernel 5.8), so fall back to going through /proc/ + * if unavailable. */ + + if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, /* flags = */ 0) < 0) { if (errno != ENOENT) return -errno; - if (proc_mounted() == 0) - return -ENOSYS; /* if we have no /proc/, the concept is not implementable */ - - return -ENOENT; + return proc_fd_enoent_errno(); } return 0; @@ -366,9 +371,21 @@ int fd_warn_permissions(const char *path, int fd) { return stat_warn_permissions(path, &st); } +int touch_fd(int fd, usec_t stamp) { + assert(fd >= 0); + + if (stamp == USEC_INFINITY) + return futimens_opath(fd, /* ts= */ NULL); + + struct timespec ts[2]; + timespec_store(ts + 0, stamp); + ts[1] = ts[0]; + return futimens_opath(fd, ts); +} + int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) { _cleanup_close_ int fd = -EBADF; - int r, ret; + int ret; assert(path); @@ -400,21 +417,10 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi * something fchown(), fchmod(), futimensat() don't allow. */ ret = fchmod_and_chown(fd, mode, uid, gid); - if (stamp != USEC_INFINITY) { - struct timespec ts[2]; - - timespec_store(&ts[0], stamp); - ts[1] = ts[0]; - r = futimens_opath(fd, ts); - } else - r = futimens_opath(fd, NULL); - if (r < 0 && ret >= 0) - return r; - - return ret; + return RET_GATHER(ret, touch_fd(fd, stamp)); } -int symlink_idempotent(const char *from, const char *to, bool make_relative) { +int symlinkat_idempotent(const char *from, int atfd, const char *to, bool make_relative) { _cleanup_free_ char *relpath = NULL; int r; @@ -429,13 +435,13 @@ int symlink_idempotent(const char *from, const char *to, bool make_relative) { from = relpath; } - if (symlink(from, to) < 0) { + if (symlinkat(from, atfd, to) < 0) { _cleanup_free_ char *p = NULL; if (errno != EEXIST) return -errno; - r = readlink_malloc(to, &p); + r = readlinkat_malloc(atfd, to, &p); if (r == -EINVAL) /* Not a symlink? In that case return the original error we encountered: -EEXIST */ return -EEXIST; if (r < 0) /* Any other error? In that case propagate it as is */ @@ -622,17 +628,18 @@ static int tmp_dir_internal(const char *def, const char **ret) { return 0; } - k = is_dir(def, true); + k = is_dir(def, /* follow = */ true); if (k == 0) k = -ENOTDIR; if (k < 0) - return r < 0 ? r : k; + return RET_GATHER(r, k); *ret = def; return 0; } int var_tmp_dir(const char **ret) { + assert(ret); /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus * even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is @@ -643,6 +650,7 @@ int var_tmp_dir(const char **ret) { } int tmp_dir(const char **ret) { + assert(ret); /* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually * backed by an in-memory file system: /tmp. */ @@ -651,6 +659,8 @@ int tmp_dir(const char **ret) { } int unlink_or_warn(const char *filename) { + assert(filename); + if (unlink(filename) < 0 && errno != ENOENT) /* If the file doesn't exist and the fs simply was read-only (in which * case unlink() returns EROFS even if the file doesn't exist), don't @@ -662,39 +672,35 @@ int unlink_or_warn(const char *filename) { } int access_fd(int fd, int mode) { + assert(fd >= 0); + /* Like access() but operates on an already open fd */ + if (faccessat(fd, "", mode, AT_EMPTY_PATH) >= 0) + return 0; + if (errno != EINVAL) + return -errno; + + /* Support for AT_EMPTY_PATH is added rather late (kernel 5.8), so fall back to going through /proc/ + * if unavailable. */ + if (access(FORMAT_PROC_FD_PATH(fd), mode) < 0) { if (errno != ENOENT) return -errno; - /* ENOENT can mean two things: that the fd does not exist or that /proc is not mounted. Let's - * make things debuggable and distinguish the two. */ - - if (proc_mounted() == 0) - return -ENOSYS; /* /proc is not available or not set up properly, we're most likely in some chroot - * environment. */ - - return -EBADF; /* The directory exists, hence it's the fd that doesn't. */ + return proc_fd_enoent_errno(); } return 0; } -void unlink_tempfilep(char (*p)[]) { - /* If the file is created with mkstemp(), it will (almost always) - * change the suffix. Treat this as a sign that the file was - * successfully created. We ignore both the rare case where the - * original suffix is used and unlink failures. */ - if (!endswith(*p, ".XXXXXX")) - (void) unlink(*p); -} - int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) { _cleanup_close_ int truncate_fd = -EBADF; struct stat st; off_t l, bs; + assert(fd >= 0 || fd == AT_FDCWD); + assert(name); assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0); /* Operates like unlinkat() but also deallocates the file contents if it is a regular file and there's no other @@ -1014,7 +1020,7 @@ int parse_cifs_service( return 0; } -int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { +int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode) { _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF; _cleanup_free_ char *fname = NULL, *parent = NULL; int r; @@ -1050,7 +1056,7 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { path = fname; } - fd = xopenat(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, /* xopen_flags = */ 0, mode); + fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode); if (IN_SET(fd, -ELOOP, -ENOTDIR)) return -EEXIST; if (fd < 0) @@ -1060,59 +1066,66 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { } int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created) { - unsigned attempts = 7; int fd; /* Just like openat(), but adds one thing: optionally returns whether we created the file anew or if * it already existed before. This is only relevant if O_CREAT is set without O_EXCL, and thus will - * shortcut to openat() otherwise */ - - if (!ret_newly_created) - return RET_NERRNO(openat(dirfd, pathname, flags, mode)); + * shortcut to openat() otherwise. + * + * Note that this routine is a bit more strict with symlinks than regular openat() is. If O_NOFOLLOW + * is not specified, then we'll follow the symlink when opening an existing file but we will *not* + * follow it when creating a new one (because that's a terrible UNIX misfeature and generally a + * security hole). */ if (!FLAGS_SET(flags, O_CREAT) || FLAGS_SET(flags, O_EXCL)) { fd = openat(dirfd, pathname, flags, mode); if (fd < 0) return -errno; - *ret_newly_created = FLAGS_SET(flags, O_CREAT); + if (ret_newly_created) + *ret_newly_created = FLAGS_SET(flags, O_CREAT); return fd; } - for (;;) { + for (unsigned attempts = 7;;) { /* First, attempt to open without O_CREAT/O_EXCL, i.e. open existing file */ fd = openat(dirfd, pathname, flags & ~(O_CREAT | O_EXCL), mode); if (fd >= 0) { - *ret_newly_created = false; + if (ret_newly_created) + *ret_newly_created = false; return fd; } if (errno != ENOENT) return -errno; - /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL. */ - fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL, mode); + /* So the file didn't exist yet, hence create it with O_CREAT/O_EXCL/O_NOFOLLOW. */ + fd = openat(dirfd, pathname, flags | O_CREAT | O_EXCL | O_NOFOLLOW, mode); if (fd >= 0) { - *ret_newly_created = true; + if (ret_newly_created) + *ret_newly_created = true; return fd; } if (errno != EEXIST) return -errno; - /* Hmm, so now we got EEXIST? So it apparently exists now? If so, let's try to open again - * without the two flags. But let's not spin forever, hence put a limit on things */ + /* Hmm, so now we got EEXIST? Then someone might have created the file between the first and + * second call to openat(). Let's try again but with a limit so we don't spin forever. */ if (--attempts == 0) /* Give up eventually, somebody is playing with us */ return -EEXIST; } } -int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) { +int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) { _cleanup_close_ int fd = -EBADF; - bool made = false; + bool made_dir = false, made_file = false; int r; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + /* An inode cannot be both a directory and a regular file at the same time. */ + assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR))); + /* This is like openat(), but has a few tricks up its sleeves, extending behaviour: * * • O_DIRECTORY|O_CREAT is supported, which causes a directory to be created, and immediately @@ -1121,17 +1134,37 @@ int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags * • If O_CREAT is used with XO_LABEL, any created file will be immediately relabelled. * * • If the path is specified NULL or empty, behaves like fd_reopen(). + * + * • If XO_NOCOW is specified will turn on the NOCOW btrfs flag on the file, if available. + * + * • if XO_REGULAR is specified will return an error if inode is not a regular file. + * + * • If mode is specified as MODE_INVALID, we'll use 0755 for dirs, and 0644 for regular files. */ + if (mode == MODE_INVALID) + mode = (open_flags & O_DIRECTORY) ? 0755 : 0644; + if (isempty(path)) { assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL)); + + if (FLAGS_SET(xopen_flags, XO_REGULAR)) { + r = fd_verify_regular(dir_fd); + if (r < 0) + return r; + } + return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW); } + bool call_label_ops_post = false; + if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) { r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG); if (r < 0) return r; + + call_label_ops_post = true; } if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) { @@ -1142,52 +1175,103 @@ int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags if (r == -EEXIST) { if (FLAGS_SET(open_flags, O_EXCL)) return -EEXIST; - - made = false; } else if (r < 0) return r; else - made = true; - - if (FLAGS_SET(xopen_flags, XO_LABEL)) { - r = label_ops_post(dir_fd, path); - if (r < 0) - return r; - } + made_dir = true; open_flags &= ~(O_EXCL|O_CREAT); - xopen_flags &= ~XO_LABEL; } - fd = RET_NERRNO(openat(dir_fd, path, open_flags, mode)); - if (fd < 0) { - if (IN_SET(fd, - /* We got ENOENT? then someone else immediately removed it after we - * created it. In that case let's return immediately without unlinking - * anything, because there simply isn't anything to unlink anymore. */ - -ENOENT, - /* is a symlink? exists already → created by someone else, don't unlink */ - -ELOOP, - /* not a directory? exists already → created by someone else, don't unlink */ - -ENOTDIR)) - return fd; + if (FLAGS_SET(xopen_flags, XO_REGULAR)) { + /* Guarantee we return a regular fd only, and don't open the file unless we verified it + * first */ - if (made) - (void) unlinkat(dir_fd, path, AT_REMOVEDIR); + if (FLAGS_SET(open_flags, O_PATH)) { + fd = openat(dir_fd, path, open_flags, mode); + if (fd < 0) { + r = -errno; + goto error; + } - return fd; + r = fd_verify_regular(fd); + if (r < 0) + goto error; + + } else if (FLAGS_SET(open_flags, O_CREAT|O_EXCL)) { + /* In O_EXCL mode we can just create the thing, everything is dealt with for us */ + fd = openat(dir_fd, path, open_flags, mode); + if (fd < 0) { + r = -errno; + goto error; + } + + made_file = true; + } else { + /* Otherwise pin the inode first via O_PATH */ + _cleanup_close_ int inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|(open_flags & O_NOFOLLOW)); + if (inode_fd < 0) { + if (errno != ENOENT || !FLAGS_SET(open_flags, O_CREAT)) { + r = -errno; + goto error; + } + + /* Doesn't exist yet, then try to create it */ + fd = openat(dir_fd, path, open_flags|O_CREAT|O_EXCL, mode); + if (fd < 0) { + r = -errno; + goto error; + } + + made_file = true; + } else { + /* OK, we pinned it. Now verify it's actually a regular file, and then reopen it */ + r = fd_verify_regular(inode_fd); + if (r < 0) + goto error; + + fd = fd_reopen(inode_fd, open_flags & ~(O_NOFOLLOW|O_CREAT)); + if (fd < 0) { + r = fd; + goto error; + } + } + } + } else { + fd = openat_report_new(dir_fd, path, open_flags, mode, &made_file); + if (fd < 0) { + r = fd; + goto error; + } } - if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) { - r = label_ops_post(dir_fd, path); + if (call_label_ops_post) { + call_label_ops_post = false; + + r = label_ops_post(fd, /* path= */ NULL, made_file || made_dir); if (r < 0) - return r; + goto error; + } + + if (FLAGS_SET(xopen_flags, XO_NOCOW)) { + r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL); + if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(r)) + goto error; } return TAKE_FD(fd); + +error: + if (call_label_ops_post) + (void) label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : path, made_dir || made_file); + + if (made_dir || made_file) + (void) unlinkat(dir_fd, path, made_dir ? AT_REMOVEDIR : 0); + + return r; } -int xopenat_lock( +int xopenat_lock_full( int dir_fd, const char *path, int open_flags, @@ -1210,7 +1294,7 @@ int xopenat_lock( for (;;) { struct stat st; - fd = xopenat(dir_fd, path, open_flags, xopen_flags, mode); + fd = xopenat_full(dir_fd, path, open_flags, xopen_flags, mode); if (fd < 0) return fd; @@ -1232,3 +1316,102 @@ int xopenat_lock( return TAKE_FD(fd); } + +int link_fd(int fd, int newdirfd, const char *newpath) { + int r, k; + + assert(fd >= 0); + assert(newdirfd >= 0 || newdirfd == AT_FDCWD); + assert(newpath); + + /* Try linking via /proc/self/fd/ first. */ + r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW)); + if (r != -ENOENT) + return r; + + /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a + * more recent kernel, but does not require /proc/ mounted) */ + k = proc_mounted(); + if (k < 0) + return r; + if (k > 0) + return -EBADF; + + return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH)); +} + +int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { + _cleanup_close_ int old_fd = -EBADF; + int r; + + assert(olddirfd >= 0 || olddirfd == AT_FDCWD); + assert(newdirfd >= 0 || newdirfd == AT_FDCWD); + assert(!isempty(newpath)); /* source path is optional, but the target path is not */ + + /* Like linkat() but replaces the target if needed. Is a NOP if source and target already share the + * same inode. */ + + if (olddirfd == AT_FDCWD && isempty(oldpath)) /* Refuse operating on the cwd (which is a dir, and dirs can't be hardlinked) */ + return -EISDIR; + + if (path_implies_directory(oldpath)) /* Refuse these definite directories early */ + return -EISDIR; + + if (path_implies_directory(newpath)) + return -EISDIR; + + /* First, try to link this directly */ + if (oldpath) + r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0)); + else + r = link_fd(olddirfd, newdirfd, newpath); + if (r >= 0) + return 0; + if (r != -EEXIST) + return r; + + old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC); + if (old_fd < 0) + return old_fd; + + struct stat old_st; + if (fstat(old_fd, &old_st) < 0) + return -errno; + + if (S_ISDIR(old_st.st_mode)) /* Don't bother if we are operating on a directory */ + return -EISDIR; + + struct stat new_st; + if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0) + return -errno; + + if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */ + return -EEXIST; + + if (stat_inode_same(&old_st, &new_st)) /* Already the same inode? Then shortcut this */ + return 0; + + _cleanup_free_ char *tmp_path = NULL; + r = tempfn_random(newpath, /* extra= */ NULL, &tmp_path); + if (r < 0) + return r; + + r = link_fd(old_fd, newdirfd, tmp_path); + if (r < 0) { + if (!ERRNO_IS_PRIVILEGE(r)) + return r; + + /* If that didn't work due to permissions then go via the path of the dentry */ + r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, tmp_path, 0)); + if (r < 0) + return r; + } + + r = RET_NERRNO(renameat(newdirfd, tmp_path, newdirfd, newpath)); + if (r < 0) { + (void) unlinkat(newdirfd, tmp_path, /* flags= */ 0); + return r; + } + + return 0; +} diff --git a/src/libnm-systemd-shared/src/basic/fs-util.h b/src/libnm-systemd-shared/src/basic/fs-util.h index 1023ab73ca..eb031a0ccd 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.h +++ b/src/libnm-systemd-shared/src/basic/fs-util.h @@ -28,9 +28,11 @@ int rmdir_parents(const char *path, const char *stop); int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); int readlinkat_malloc(int fd, const char *p, char **ret); -int readlink_malloc(const char *p, char **r); +static inline int readlink_malloc(const char *p, char **ret) { + return readlinkat_malloc(AT_FDCWD, p, ret); +} int readlink_value(const char *p, char **ret); -int readlink_and_make_absolute(const char *p, char **r); +int readlink_and_make_absolute(const char *p, char **ret); int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid); static inline int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { @@ -49,16 +51,21 @@ int futimens_opath(int fd, const struct timespec ts[2]); int fd_warn_permissions(const char *path, int fd); int stat_warn_permissions(const char *path, const struct stat *st); -#define laccess(path, mode) \ +#define access_nofollow(path, mode) \ RET_NERRNO(faccessat(AT_FDCWD, (path), (mode), AT_SYMLINK_NOFOLLOW)) +int touch_fd(int fd, usec_t stamp); + int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode); static inline int touch(const char *path) { return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); } -int symlink_idempotent(const char *from, const char *to, bool make_relative); +int symlinkat_idempotent(const char *from, int atfd, const char *to, bool make_relative); +static inline int symlink_idempotent(const char *from, const char *to, bool make_relative) { + return symlinkat_idempotent(from, AT_FDCWD, to, make_relative); +} int symlinkat_atomic_full(const char *from, int atfd, const char *to, bool make_relative); static inline int symlink_atomic(const char *from, const char *to) { @@ -105,8 +112,6 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free); int access_fd(int fd, int mode); -void unlink_tempfilep(char (*p)[]); - typedef enum UnlinkDeallocateFlags { UNLINK_REMOVEDIR = 1 << 0, UNLINK_ERASE = 1 << 1, @@ -128,15 +133,42 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size); int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char **ret_path); -int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode); +typedef enum XOpenFlags { + XO_LABEL = 1 << 0, /* When creating: relabel */ + XO_SUBVOLUME = 1 << 1, /* When creating as directory: make it a subvolume */ + XO_NOCOW = 1 << 2, /* Enable NOCOW mode after opening */ + XO_REGULAR = 1 << 3, /* Fail if the inode is not a regular file */ +} XOpenFlags; + +int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode); +static inline int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { + return open_mkdir_at_full(dirfd, path, flags, 0, mode); +} +static inline int open_mkdir(const char *path, int flags, mode_t mode) { + return open_mkdir_at_full(AT_FDCWD, path, flags, 0, mode); +} int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created); -typedef enum XOpenFlags { - XO_LABEL = 1 << 0, - XO_SUBVOLUME = 1 << 1, -} XOpenFlags; +int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode); +static inline int xopenat(int dir_fd, const char *path, int open_flags) { + return xopenat_full(dir_fd, path, open_flags, 0, 0); +} -int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode); +int xopenat_lock_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode, LockType locktype, int operation); +static inline int xopenat_lock(int dir_fd, const char *path, int open_flags, LockType locktype, int operation) { + return xopenat_lock_full(dir_fd, path, open_flags, 0, 0, locktype, operation); +} -int xopenat_lock(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode, LockType locktype, int operation); +int link_fd(int fd, int newdirfd, const char *newpath); + +int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); + +static inline int at_flags_normalize_nofollow(int flags) { + if (FLAGS_SET(flags, AT_SYMLINK_FOLLOW)) { + assert(!FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW)); + flags &= ~AT_SYMLINK_FOLLOW; + } else + flags |= AT_SYMLINK_NOFOLLOW; + return flags; +} diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.c b/src/libnm-systemd-shared/src/basic/glyph-util.c index 803bdd90e2..64f6685cb4 100644 --- a/src/libnm-systemd-shared/src/basic/glyph-util.c +++ b/src/libnm-systemd-shared/src/basic/glyph-util.c @@ -23,7 +23,7 @@ bool emoji_enabled(void) { return cached_emoji_enabled; } -const char *special_glyph_full(SpecialGlyph code, bool force_utf) { +const char* special_glyph_full(SpecialGlyph code, bool force_utf) { /* A list of a number of interesting unicode glyphs we can use to decorate our output. It's probably wise to be * conservative here, and primarily stick to the glyphs defined in the eurlatgr font, so that display still @@ -41,6 +41,8 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { [SPECIAL_GLYPH_TREE_SPACE] = " ", [SPECIAL_GLYPH_TREE_TOP] = ",-", [SPECIAL_GLYPH_VERTICAL_DOTTED] = ":", + [SPECIAL_GLYPH_HORIZONTAL_DOTTED] = "-", + [SPECIAL_GLYPH_HORIZONTAL_FAT] = "=", [SPECIAL_GLYPH_TRIANGULAR_BULLET] = ">", [SPECIAL_GLYPH_BLACK_CIRCLE] = "*", [SPECIAL_GLYPH_WHITE_CIRCLE] = "*", @@ -74,6 +76,12 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { [SPECIAL_GLYPH_SPARKLES] = "*", [SPECIAL_GLYPH_LOW_BATTERY] = "!", [SPECIAL_GLYPH_WARNING_SIGN] = "!", + [SPECIAL_GLYPH_RED_CIRCLE] = "o", + [SPECIAL_GLYPH_YELLOW_CIRCLE] = "o", + [SPECIAL_GLYPH_BLUE_CIRCLE] = "o", + [SPECIAL_GLYPH_GREEN_CIRCLE] = "o", + [SPECIAL_GLYPH_SUPERHERO] = "S", + [SPECIAL_GLYPH_IDCARD] = "@", }, /* UTF-8 */ @@ -87,6 +95,8 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { /* Single glyphs in both cases */ [SPECIAL_GLYPH_VERTICAL_DOTTED] = u8"┆", + [SPECIAL_GLYPH_HORIZONTAL_DOTTED] = u8"┄", + [SPECIAL_GLYPH_HORIZONTAL_FAT] = u8"━", [SPECIAL_GLYPH_TRIANGULAR_BULLET] = u8"‣", [SPECIAL_GLYPH_BLACK_CIRCLE] = u8"●", [SPECIAL_GLYPH_WHITE_CIRCLE] = u8"○", @@ -136,6 +146,13 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { [SPECIAL_GLYPH_WARNING_SIGN] = u8"⚠️", [SPECIAL_GLYPH_COMPUTER_DISK] = u8"💽", [SPECIAL_GLYPH_WORLD] = u8"🌍", + + [SPECIAL_GLYPH_RED_CIRCLE] = u8"🔴", + [SPECIAL_GLYPH_YELLOW_CIRCLE] = u8"🟡", + [SPECIAL_GLYPH_BLUE_CIRCLE] = u8"🔵", + [SPECIAL_GLYPH_GREEN_CIRCLE] = u8"🟢", + [SPECIAL_GLYPH_SUPERHERO] = u8"🦸", + [SPECIAL_GLYPH_IDCARD] = u8"🪪", }, }; diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.h b/src/libnm-systemd-shared/src/basic/glyph-util.h index a7709976e1..ca4d4eda71 100644 --- a/src/libnm-systemd-shared/src/basic/glyph-util.h +++ b/src/libnm-systemd-shared/src/basic/glyph-util.h @@ -13,6 +13,8 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_TREE_SPACE, SPECIAL_GLYPH_TREE_TOP, SPECIAL_GLYPH_VERTICAL_DOTTED, + SPECIAL_GLYPH_HORIZONTAL_DOTTED, + SPECIAL_GLYPH_HORIZONTAL_FAT, SPECIAL_GLYPH_TRIANGULAR_BULLET, SPECIAL_GLYPH_BLACK_CIRCLE, SPECIAL_GLYPH_WHITE_CIRCLE, @@ -49,22 +51,28 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_WARNING_SIGN, SPECIAL_GLYPH_COMPUTER_DISK, SPECIAL_GLYPH_WORLD, + SPECIAL_GLYPH_RED_CIRCLE, + SPECIAL_GLYPH_YELLOW_CIRCLE, + SPECIAL_GLYPH_BLUE_CIRCLE, + SPECIAL_GLYPH_GREEN_CIRCLE, + SPECIAL_GLYPH_SUPERHERO, + SPECIAL_GLYPH_IDCARD, _SPECIAL_GLYPH_MAX, _SPECIAL_GLYPH_INVALID = -EINVAL, } SpecialGlyph; bool emoji_enabled(void); -const char *special_glyph_full(SpecialGlyph code, bool force_utf) _const_; +const char* special_glyph_full(SpecialGlyph code, bool force_utf) _const_; -static inline const char *special_glyph(SpecialGlyph code) { +static inline const char* special_glyph(SpecialGlyph code) { return special_glyph_full(code, false); } -static inline const char *special_glyph_check_mark(bool b) { +static inline const char* special_glyph_check_mark(bool b) { return b ? special_glyph(SPECIAL_GLYPH_CHECK_MARK) : special_glyph(SPECIAL_GLYPH_CROSS_MARK); } -static inline const char *special_glyph_check_mark_space(bool b) { +static inline const char* special_glyph_check_mark_space(bool b) { return b ? special_glyph(SPECIAL_GLYPH_CHECK_MARK) : " "; } diff --git a/src/libnm-systemd-shared/src/basic/hashmap.c b/src/libnm-systemd-shared/src/basic/hashmap.c index 894760ca60..951f63ae6d 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.c +++ b/src/libnm-systemd-shared/src/basic/hashmap.c @@ -874,6 +874,26 @@ int _ordered_hashmap_ensure_put(OrderedHashmap **h, const struct hash_ops *hash_ return ordered_hashmap_put(*h, key, value); } +int _ordered_hashmap_ensure_replace(OrderedHashmap **h, const struct hash_ops *hash_ops, const void *key, void *value HASHMAP_DEBUG_PARAMS) { + int r; + + r = _ordered_hashmap_ensure_allocated(h, hash_ops HASHMAP_DEBUG_PASS_ARGS); + if (r < 0) + return r; + + return ordered_hashmap_replace(*h, key, value); +} + +int _hashmap_ensure_replace(Hashmap **h, const struct hash_ops *hash_ops, const void *key, void *value HASHMAP_DEBUG_PARAMS) { + int r; + + r = _hashmap_ensure_allocated(h, hash_ops HASHMAP_DEBUG_PASS_ARGS); + if (r < 0) + return r; + + return hashmap_replace(*h, key, value); +} + static void hashmap_free_no_clear(HashmapBase *h) { assert(!h->has_indirect); assert(h->n_direct_entries == 0); @@ -2120,24 +2140,27 @@ static int hashmap_entry_compare( return compare((*a)->key, (*b)->key); } -int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { - _cleanup_free_ struct hashmap_base_entry **entries = NULL; +static int _hashmap_dump_entries_sorted( + HashmapBase *h, + void ***ret, + size_t *ret_n) { + _cleanup_free_ void **entries = NULL; Iterator iter; unsigned idx; size_t n = 0; assert(ret); + assert(ret_n); if (_hashmap_size(h) == 0) { *ret = NULL; - if (ret_n) - *ret_n = 0; + *ret_n = 0; return 0; } /* We append one more element than needed so that the resulting array can be used as a strv. We * don't count this entry in the returned size. */ - entries = new(struct hashmap_base_entry*, _hashmap_size(h) + 1); + entries = new(void*, _hashmap_size(h) + 1); if (!entries) return -ENOMEM; @@ -2147,13 +2170,47 @@ int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { assert(n == _hashmap_size(h)); entries[n] = NULL; - typesafe_qsort_r(entries, n, hashmap_entry_compare, h->hash_ops->compare); + typesafe_qsort_r((struct hashmap_base_entry**) entries, n, + hashmap_entry_compare, h->hash_ops->compare); + + *ret = TAKE_PTR(entries); + *ret_n = n; + return 0; +} + +int _hashmap_dump_keys_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { + _cleanup_free_ void **entries = NULL; + size_t n; + int r; + + r = _hashmap_dump_entries_sorted(h, &entries, &n); + if (r < 0) + return r; /* Reuse the array. */ FOREACH_ARRAY(e, entries, n) - *e = entry_value(h, *e); + *e = (void*) (*(struct hashmap_base_entry**) e)->key; - *ret = (void**) TAKE_PTR(entries); + *ret = TAKE_PTR(entries); + if (ret_n) + *ret_n = n; + return 0; +} + +int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { + _cleanup_free_ void **entries = NULL; + size_t n; + int r; + + r = _hashmap_dump_entries_sorted(h, &entries, &n); + if (r < 0) + return r; + + /* Reuse the array. */ + FOREACH_ARRAY(e, entries, n) + *e = entry_value(h, *(struct hashmap_base_entry**) e); + + *ret = TAKE_PTR(entries); if (ret_n) *ret_n = n; return 0; diff --git a/src/libnm-systemd-shared/src/basic/hashmap.h b/src/libnm-systemd-shared/src/basic/hashmap.h index 233f1d7a1e..01a4fb3204 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.h +++ b/src/libnm-systemd-shared/src/basic/hashmap.h @@ -39,8 +39,8 @@ typedef struct IteratedCache IteratedCache; /* Caches the iterated order of on * by hashmap users, so the definition has to be here. Do not use its fields * directly. */ typedef struct { - unsigned idx; /* index of an entry to be iterated next */ const void *next_key; /* expected value of that entry's key pointer */ + unsigned idx; /* index of an entry to be iterated next */ #if ENABLE_DEBUG_HASHMAP unsigned put_count; /* hashmap's put_count recorded at start of iteration */ unsigned rem_count; /* hashmap's rem_count in previous iteration */ @@ -130,14 +130,19 @@ HashmapBase* _hashmap_copy(HashmapBase *h HASHMAP_DEBUG_PARAMS); int _hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); int _hashmap_ensure_put(Hashmap **h, const struct hash_ops *hash_ops, const void *key, void *value HASHMAP_DEBUG_PARAMS); int _ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +int _hashmap_ensure_replace(Hashmap **h, const struct hash_ops *hash_ops, const void *key, void *value HASHMAP_DEBUG_PARAMS); #define hashmap_ensure_allocated(h, ops) _hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) #define hashmap_ensure_put(s, ops, key, value) _hashmap_ensure_put(s, ops, key, value HASHMAP_DEBUG_SRC_ARGS) #define ordered_hashmap_ensure_allocated(h, ops) _ordered_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) +#define hashmap_ensure_replace(s, ops, key, value) _hashmap_ensure_replace(s, ops, key, value HASHMAP_DEBUG_SRC_ARGS) int _ordered_hashmap_ensure_put(OrderedHashmap **h, const struct hash_ops *hash_ops, const void *key, void *value HASHMAP_DEBUG_PARAMS); #define ordered_hashmap_ensure_put(s, ops, key, value) _ordered_hashmap_ensure_put(s, ops, key, value HASHMAP_DEBUG_SRC_ARGS) +int _ordered_hashmap_ensure_replace(OrderedHashmap **h, const struct hash_ops *hash_ops, const void *key, void *value HASHMAP_DEBUG_PARAMS); +#define ordered_hashmap_ensure_replace(s, ops, key, value) _ordered_hashmap_ensure_replace(s, ops, key, value HASHMAP_DEBUG_SRC_ARGS) + IteratedCache* _hashmap_iterated_cache_new(HashmapBase *h); static inline IteratedCache* hashmap_iterated_cache_new(Hashmap *h) { return (IteratedCache*) _hashmap_iterated_cache_new(HASHMAP_BASE(h)); @@ -409,6 +414,14 @@ static inline int set_dump_sorted(Set *h, void ***ret, size_t *ret_n) { return _hashmap_dump_sorted(HASHMAP_BASE(h), ret, ret_n); } +int _hashmap_dump_keys_sorted(HashmapBase *h, void ***ret, size_t *ret_n); +static inline int hashmap_dump_keys_sorted(Hashmap *h, void ***ret, size_t *ret_n) { + return _hashmap_dump_keys_sorted(HASHMAP_BASE(h), ret, ret_n); +} +static inline int ordered_hashmap_dump_keys_sorted(OrderedHashmap *h, void ***ret, size_t *ret_n) { + return _hashmap_dump_keys_sorted(HASHMAP_BASE(h), ret, ret_n); +} + /* * Hashmaps are iterated in unpredictable order. * OrderedHashmaps are an exception to this. They are iterated in the order diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.c b/src/libnm-systemd-shared/src/basic/hexdecoct.c index 4cb67d94a4..1d8e60330c 100644 --- a/src/libnm-systemd-shared/src/basic/hexdecoct.c +++ b/src/libnm-systemd-shared/src/basic/hexdecoct.c @@ -36,7 +36,7 @@ int undecchar(char c) { } char hexchar(int x) { - static const char table[16] = "0123456789abcdef"; + static const char table[] = "0123456789abcdef"; return table[x & 15]; } @@ -55,7 +55,7 @@ int unhexchar(char c) { return -EINVAL; } -char *hexmem(const void *p, size_t l) { +char* hexmem(const void *p, size_t l) { const uint8_t *x; char *r, *z; @@ -168,8 +168,8 @@ int unhexmem_full( * useful when representing NSEC3 hashes, as one can then verify the * order of hashes directly from their representation. */ char base32hexchar(int x) { - static const char table[32] = "0123456789" - "ABCDEFGHIJKLMNOPQRSTUV"; + static const char table[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUV"; return table[x & 31]; } @@ -188,7 +188,7 @@ int unbase32hexchar(char c) { return -EINVAL; } -char *base32hexmem(const void *p, size_t l, bool padding) { +char* base32hexmem(const void *p, size_t l, bool padding) { char *r, *z; const uint8_t *x; size_t len; @@ -519,9 +519,9 @@ int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_l /* https://tools.ietf.org/html/rfc4648#section-4 */ char base64char(int x) { - static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; + static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; return table[x & 63]; } @@ -529,9 +529,9 @@ char base64char(int x) { * since we don't want "/" appear in interface names (since interfaces appear in sysfs as filenames). * See section #5 of RFC 4648. */ char urlsafe_base64char(int x) { - static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789-_"; + static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_"; return table[x & 63]; } @@ -866,6 +866,9 @@ void hexdump(FILE *f, const void *p, size_t s) { assert(b || s == 0); + if (s == SIZE_MAX) + s = strlen(p); + if (!f) f = stdout; diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.h b/src/libnm-systemd-shared/src/basic/hexdecoct.h index 0a10af3e16..d160ca28c9 100644 --- a/src/libnm-systemd-shared/src/basic/hexdecoct.h +++ b/src/libnm-systemd-shared/src/basic/hexdecoct.h @@ -17,7 +17,7 @@ int undecchar(char c) _const_; char hexchar(int x) _const_; int unhexchar(char c) _const_; -char *hexmem(const void *p, size_t l); +char* hexmem(const void *p, size_t l); int unhexmem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size); static inline int unhexmem(const char *p, void **ret_data, size_t *ret_size) { return unhexmem_full(p, SIZE_MAX, false, ret_data, ret_size); @@ -30,7 +30,7 @@ char base64char(int x) _const_; char urlsafe_base64char(int x) _const_; int unbase64char(char c) _const_; -char *base32hexmem(const void *p, size_t l, bool padding); +char* base32hexmem(const void *p, size_t l, bool padding); int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len); ssize_t base64mem_full(const void *p, size_t l, size_t line_break, char **ret); diff --git a/src/libnm-systemd-shared/src/basic/in-addr-util.c b/src/libnm-systemd-shared/src/basic/in-addr-util.c index 8bd9c75d59..fa28d79dcb 100644 --- a/src/libnm-systemd-shared/src/basic/in-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/in-addr-util.c @@ -91,14 +91,26 @@ bool in6_addr_is_link_local_all_nodes(const struct in6_addr *a) { be32toh(a->s6_addr32[3]) == UINT32_C(0x00000001); } +bool in4_addr_is_multicast(const struct in_addr *a) { + assert(a); + + return IN_MULTICAST(be32toh(a->s_addr)); +} + +bool in6_addr_is_multicast(const struct in6_addr *a) { + assert(a); + + return IN6_IS_ADDR_MULTICAST(a); +} + int in_addr_is_multicast(int family, const union in_addr_union *u) { assert(u); if (family == AF_INET) - return IN_MULTICAST(be32toh(u->in.s_addr)); + return in4_addr_is_multicast(&u->in); if (family == AF_INET6) - return IN6_IS_ADDR_MULTICAST(&u->in6); + return in6_addr_is_multicast(&u->in6); return -EAFNOSUPPORT; } @@ -182,6 +194,52 @@ int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_ return -EAFNOSUPPORT; } +bool in4_addr_prefix_intersect( + const struct in_addr *a, + unsigned aprefixlen, + const struct in_addr *b, + unsigned bprefixlen) { + + assert(a); + assert(b); + + unsigned m = MIN3(aprefixlen, bprefixlen, (unsigned) (sizeof(struct in_addr) * 8)); + if (m == 0) + return true; /* Let's return earlier, to avoid shift by 32. */ + + uint32_t x = be32toh(a->s_addr ^ b->s_addr); + uint32_t n = 0xFFFFFFFFUL << (32 - m); + return (x & n) == 0; +} + +bool in6_addr_prefix_intersect( + const struct in6_addr *a, + unsigned aprefixlen, + const struct in6_addr *b, + unsigned bprefixlen) { + + assert(a); + assert(b); + + unsigned m = MIN3(aprefixlen, bprefixlen, (unsigned) (sizeof(struct in6_addr) * 8)); + if (m == 0) + return true; + + for (size_t i = 0; i < sizeof(struct in6_addr); i++) { + uint8_t x = a->s6_addr[i] ^ b->s6_addr[i]; + uint8_t n = m < 8 ? (0xFF << (8 - m)) : 0xFF; + if ((x & n) != 0) + return false; + + if (m <= 8) + break; + + m -= 8; + } + + return true; +} + int in_addr_prefix_intersect( int family, const union in_addr_union *a, @@ -189,51 +247,16 @@ int in_addr_prefix_intersect( const union in_addr_union *b, unsigned bprefixlen) { - unsigned m; - assert(a); assert(b); - /* Checks whether there are any addresses that are in both networks */ + /* Checks whether there are any addresses that are in both networks. */ - m = MIN(aprefixlen, bprefixlen); + if (family == AF_INET) + return in4_addr_prefix_intersect(&a->in, aprefixlen, &b->in, bprefixlen); - if (family == AF_INET) { - uint32_t x, nm; - - x = be32toh(a->in.s_addr ^ b->in.s_addr); - nm = m == 0 ? 0 : 0xFFFFFFFFUL << (32 - m); - - return (x & nm) == 0; - } - - if (family == AF_INET6) { - unsigned i; - - if (m > 128) - m = 128; - - for (i = 0; i < 16; i++) { - uint8_t x, nm; - - x = a->in6.s6_addr[i] ^ b->in6.s6_addr[i]; - - if (m < 8) - nm = 0xFF << (8 - m); - else - nm = 0xFF; - - if ((x & nm) != 0) - return 0; - - if (m > 8) - m -= 8; - else - m = 0; - } - - return 1; - } + if (family == AF_INET6) + return in6_addr_prefix_intersect(&a->in6, aprefixlen, &b->in6, bprefixlen); return -EAFNOSUPPORT; } @@ -867,7 +890,7 @@ int in_addr_prefix_from_string( return 0; } -int in_addr_prefix_from_string_auto_internal( +int in_addr_prefix_from_string_auto_full( const char *p, InAddrPrefixLenMode mode, int *ret_family, diff --git a/src/libnm-systemd-shared/src/basic/in-addr-util.h b/src/libnm-systemd-shared/src/basic/in-addr-util.h index 9fae3cae45..2efe9aec01 100644 --- a/src/libnm-systemd-shared/src/basic/in-addr-util.h +++ b/src/libnm-systemd-shared/src/basic/in-addr-util.h @@ -40,6 +40,8 @@ static inline bool in_addr_data_is_set(const struct in_addr_data *a) { return in_addr_data_is_null(a); } +bool in4_addr_is_multicast(const struct in_addr *a); +bool in6_addr_is_multicast(const struct in6_addr *a); int in_addr_is_multicast(int family, const union in_addr_union *u); bool in4_addr_is_link_local(const struct in_addr *a); @@ -59,7 +61,22 @@ bool in6_addr_is_ipv4_mapped_address(const struct in6_addr *a); bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b); bool in6_addr_equal(const struct in6_addr *a, const struct in6_addr *b); int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b); -int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen); +bool in4_addr_prefix_intersect( + const struct in_addr *a, + unsigned aprefixlen, + const struct in_addr *b, + unsigned bprefixlen); +bool in6_addr_prefix_intersect( + const struct in6_addr *a, + unsigned aprefixlen, + const struct in6_addr *b, + unsigned bprefixlen); +int in_addr_prefix_intersect( + int family, + const union in_addr_union *a, + unsigned aprefixlen, + const union in_addr_union *b, + unsigned bprefixlen); int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen); int in_addr_prefix_nth(int family, union in_addr_union *u, unsigned prefixlen, uint64_t nth); int in_addr_random_prefix(int family, union in_addr_union *u, unsigned prefixlen_fixed_part, unsigned prefixlen); @@ -164,9 +181,9 @@ typedef enum InAddrPrefixLenMode { PREFIXLEN_REFUSE, /* Fail with -ENOANO if prefixlen is not specified. */ } InAddrPrefixLenMode; -int in_addr_prefix_from_string_auto_internal(const char *p, InAddrPrefixLenMode mode, int *ret_family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen); +int in_addr_prefix_from_string_auto_full(const char *p, InAddrPrefixLenMode mode, int *ret_family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen); static inline int in_addr_prefix_from_string_auto(const char *p, int *ret_family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen) { - return in_addr_prefix_from_string_auto_internal(p, PREFIXLEN_FULL, ret_family, ret_prefix, ret_prefixlen); + return in_addr_prefix_from_string_auto_full(p, PREFIXLEN_FULL, ret_family, ret_prefix, ret_prefixlen); } static inline size_t FAMILY_ADDRESS_SIZE(int family) { diff --git a/src/libnm-systemd-shared/src/basic/inotify-util.c b/src/libnm-systemd-shared/src/basic/inotify-util.c index ee9b416c87..d8cbb04509 100644 --- a/src/libnm-systemd-shared/src/basic/inotify-util.c +++ b/src/libnm-systemd-shared/src/basic/inotify-util.c @@ -42,7 +42,10 @@ bool inotify_event_next( } int inotify_add_watch_fd(int fd, int what, uint32_t mask) { - int wd, r; + int wd; + + assert(fd >= 0); + assert(what >= 0); /* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */ wd = inotify_add_watch(fd, FORMAT_PROC_FD_PATH(what), mask); @@ -50,14 +53,7 @@ int inotify_add_watch_fd(int fd, int what, uint32_t mask) { if (errno != ENOENT) return -errno; - /* Didn't work with ENOENT? If so, then either /proc/ isn't mounted, or the fd is bad */ - r = proc_mounted(); - if (r == 0) - return -ENOSYS; - if (r > 0) - return -EBADF; - - return -ENOENT; /* OK, no clue, let's propagate the original error */ + return proc_fd_enoent_errno(); } return wd; diff --git a/src/libnm-systemd-shared/src/basic/iovec-util.h b/src/libnm-systemd-shared/src/basic/iovec-util.h index 8cfa5717dc..868454040b 100644 --- a/src/libnm-systemd-shared/src/basic/iovec-util.h +++ b/src/libnm-systemd-shared/src/basic/iovec-util.h @@ -6,25 +6,16 @@ #include #include "alloc-util.h" +#include "iovec-util-fundamental.h" #include "macro.h" -/* An iovec pointing to a single NUL byte */ -#define IOVEC_NUL_BYTE (const struct iovec) { \ - .iov_base = (void*) (const uint8_t[1]) { 0 }, \ - .iov_len = 1, \ - } +extern const struct iovec iovec_nul_byte; /* Points to a single NUL byte */ +extern const struct iovec iovec_empty; /* Points to an empty, but valid (i.e. non-NULL) pointer */ size_t iovec_total_size(const struct iovec *iovec, size_t n); bool iovec_increment(struct iovec *iovec, size_t n, size_t k); -/* This accepts both const and non-const pointers */ -#define IOVEC_MAKE(base, len) \ - (struct iovec) { \ - .iov_base = (void*) (base), \ - .iov_len = (len), \ - } - static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s) { assert(iovec); /* We don't use strlen_ptr() here, because we don't want to include string-util.h for now */ @@ -41,14 +32,6 @@ static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s .iov_len = STRLEN(s), \ } -static inline void iovec_done(struct iovec *iovec) { - /* A _cleanup_() helper that frees the iov_base in the iovec */ - assert(iovec); - - iovec->iov_base = mfree(iovec->iov_base); - iovec->iov_len = 0; -} - static inline void iovec_done_erase(struct iovec *iovec) { assert(iovec); @@ -56,16 +39,6 @@ static inline void iovec_done_erase(struct iovec *iovec) { iovec->iov_len = 0; } -static inline bool iovec_is_set(const struct iovec *iovec) { - /* Checks if the iovec points to a non-empty chunk of memory */ - return iovec && iovec->iov_len > 0 && iovec->iov_base; -} - -static inline bool iovec_is_valid(const struct iovec *iovec) { - /* Checks if the iovec is either NULL, empty or points to a valid bit of memory */ - return !iovec || (iovec->iov_base || iovec->iov_len == 0); -} - char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value); char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value); @@ -97,3 +70,5 @@ static inline struct iovec *iovec_memdup(const struct iovec *source, struct iove return ret; } + +struct iovec* iovec_append(struct iovec *iovec, const struct iovec *append); diff --git a/src/libnm-systemd-shared/src/basic/label.c b/src/libnm-systemd-shared/src/basic/label.c index f134e77589..6bae653188 100644 --- a/src/libnm-systemd-shared/src/basic/label.c +++ b/src/libnm-systemd-shared/src/basic/label.c @@ -4,10 +4,13 @@ #include #include "label.h" +#include "macro.h" static const LabelOps *label_ops = NULL; int label_ops_set(const LabelOps *ops) { + assert(ops); + if (label_ops) return -EBUSY; @@ -15,6 +18,10 @@ int label_ops_set(const LabelOps *ops) { return 0; } +void label_ops_reset(void) { + label_ops = NULL; +} + int label_ops_pre(int dir_fd, const char *path, mode_t mode) { if (!label_ops || !label_ops->pre) return 0; @@ -22,9 +29,9 @@ int label_ops_pre(int dir_fd, const char *path, mode_t mode) { return label_ops->pre(dir_fd, path, mode); } -int label_ops_post(int dir_fd, const char *path) { +int label_ops_post(int dir_fd, const char *path, bool created) { if (!label_ops || !label_ops->post) return 0; - return label_ops->post(dir_fd, path); + return label_ops->post(dir_fd, path, created); } diff --git a/src/libnm-systemd-shared/src/basic/label.h b/src/libnm-systemd-shared/src/basic/label.h index 9644e435a3..d001307a4f 100644 --- a/src/libnm-systemd-shared/src/basic/label.h +++ b/src/libnm-systemd-shared/src/basic/label.h @@ -1,14 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include #include typedef struct LabelOps { int (*pre)(int dir_fd, const char *path, mode_t mode); - int (*post)(int dir_fd, const char *path); + int (*post)(int dir_fd, const char *path, bool created); } LabelOps; int label_ops_set(const LabelOps *label_ops); +void label_ops_reset(void); int label_ops_pre(int dir_fd, const char *path, mode_t mode); -int label_ops_post(int dir_fd, const char *path); +int label_ops_post(int dir_fd, const char *path, bool created); diff --git a/src/libnm-systemd-shared/src/basic/locale-util.c b/src/libnm-systemd-shared/src/basic/locale-util.c index 9e70c3f01f..09bdb37300 100644 --- a/src/libnm-systemd-shared/src/basic/locale-util.c +++ b/src/libnm-systemd-shared/src/basic/locale-util.c @@ -260,7 +260,10 @@ bool locale_is_valid(const char *name) { if (!filename_is_valid(name)) return false; - if (!string_is_safe(name)) + /* Locales look like: ll_CC.ENC@variant, where ll and CC are alphabetic, ENC is alphanumeric with + * dashes, and variant seems to be alphabetic. + * See: https://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html */ + if (!in_charset(name, ALPHANUMERICAL "_.-@")) return false; return true; @@ -273,8 +276,7 @@ int locale_is_installed(const char *name) { if (STR_IN_SET(name, "C", "POSIX")) /* These ones are always OK */ return true; - _cleanup_(freelocalep) locale_t loc = - newlocale(LC_ALL_MASK, name, 0); + _cleanup_(freelocalep) locale_t loc = newlocale(LC_ALL_MASK, name, (locale_t) 0); if (loc == (locale_t) 0) return errno == ENOMEM ? -ENOMEM : false; @@ -292,7 +294,7 @@ bool is_locale_utf8(void) { if (cached_answer >= 0) goto out; - r = getenv_bool_secure("SYSTEMD_UTF8"); + r = secure_getenv_bool("SYSTEMD_UTF8"); if (r >= 0) { cached_answer = r; goto out; diff --git a/src/libnm-systemd-shared/src/basic/lock-util.h b/src/libnm-systemd-shared/src/basic/lock-util.h index 91b332f803..a67d8b2c93 100644 --- a/src/libnm-systemd-shared/src/basic/lock-util.h +++ b/src/libnm-systemd-shared/src/basic/lock-util.h @@ -2,6 +2,8 @@ #pragma once #include +/* Include here so consumers have LOCK_{EX,SH,NB} available. */ +#include typedef struct LockFile { int dir_fd; @@ -17,7 +19,7 @@ static inline int make_lock_file(const char *p, int operation, LockFile *ret) { int make_lock_file_for(const char *p, int operation, LockFile *ret); void release_lock_file(LockFile *f); -#define LOCK_FILE_INIT { .dir_fd = -EBADF, .fd = -EBADF } +#define LOCK_FILE_INIT (LockFile) { .dir_fd = -EBADF, .fd = -EBADF } /* POSIX locks with the same interface as flock(). */ int posix_lock(int fd, int operation); diff --git a/src/libnm-systemd-shared/src/basic/log.h b/src/libnm-systemd-shared/src/basic/log.h index 140e501eb1..02f3f509d1 100644 --- a/src/libnm-systemd-shared/src/basic/log.h +++ b/src/libnm-systemd-shared/src/basic/log.h @@ -18,25 +18,26 @@ struct signalfd_siginfo; typedef enum LogTarget{ LOG_TARGET_CONSOLE, - LOG_TARGET_CONSOLE_PREFIXED, LOG_TARGET_KMSG, LOG_TARGET_JOURNAL, - LOG_TARGET_JOURNAL_OR_KMSG, LOG_TARGET_SYSLOG, + LOG_TARGET_CONSOLE_PREFIXED, + LOG_TARGET_JOURNAL_OR_KMSG, LOG_TARGET_SYSLOG_OR_KMSG, LOG_TARGET_AUTO, /* console if stderr is not journal, JOURNAL_OR_KMSG otherwise */ LOG_TARGET_NULL, - _LOG_TARGET_MAX, + _LOG_TARGET_SINGLE_MAX = LOG_TARGET_SYSLOG + 1, + _LOG_TARGET_MAX = LOG_TARGET_NULL + 1, _LOG_TARGET_INVALID = -EINVAL, } LogTarget; /* This log level disables logging completely. It can only be passed to log_set_max_level() and cannot be - * used a regular log level. */ + * used as a regular log level. */ #define LOG_NULL (LOG_EMERG - 1) +assert_cc(LOG_NULL == -1); -/* Note to readers: << and >> have lower precedence (are evaluated earlier) than & and | */ -#define SYNTHETIC_ERRNO(num) (1 << 30 | (num)) -#define IS_SYNTHETIC_ERRNO(val) ((val) >> 30 & 1) +#define SYNTHETIC_ERRNO(num) (abs(num) | (1 << 30)) +#define IS_SYNTHETIC_ERRNO(val) (((val) >> 30) == 1) #define ERRNO_VALUE(val) (abs(val) & ~(1 << 30)) /* The callback function to be invoked when syntax warnings are seen @@ -48,7 +49,7 @@ static inline void clear_log_syntax_callback(dummy_t *dummy) { set_log_syntax_callback(/* cb= */ NULL, /* userdata= */ NULL); } -const char *log_target_to_string(LogTarget target) _const_; +const char* log_target_to_string(LogTarget target) _const_; LogTarget log_target_from_string(const char *s) _pure_; void log_set_target(LogTarget target); void log_set_target_and_open(LogTarget target); @@ -56,9 +57,10 @@ int log_set_target_from_string(const char *e); LogTarget log_get_target(void) _pure_; void log_settle_target(void); -void log_set_max_level(int level); +int log_set_max_level(int level); int log_set_max_level_from_string(const char *e); int log_get_max_level(void) _pure_; +int log_max_levels_to_string(int level, char **ret); void log_set_facility(int facility); @@ -83,6 +85,7 @@ int log_show_tid_from_string(const char *e); assert_cc(STRLEN(__FILE__) > STRLEN(RELATIVE_SOURCE_PATH) + 1); #define PROJECT_FILE (&__FILE__[STRLEN(RELATIVE_SOURCE_PATH) + 1]) +bool stderr_is_journal(void); int log_open(void); void log_close(void); void log_forget_fds(void); @@ -298,9 +301,10 @@ int log_emergency_level(void); #define log_dump(level, buffer) \ log_dump_internal(level, 0, PROJECT_FILE, __LINE__, __func__, buffer) -#define log_oom() log_oom_internal(LOG_ERR, PROJECT_FILE, __LINE__, __func__) -#define log_oom_debug() log_oom_internal(LOG_DEBUG, PROJECT_FILE, __LINE__, __func__) -#define log_oom_warning() log_oom_internal(LOG_WARNING, PROJECT_FILE, __LINE__, __func__) +#define log_oom_full(level) log_oom_internal(level, PROJECT_FILE, __LINE__, __func__) +#define log_oom() log_oom_full(LOG_ERR) +#define log_oom_debug() log_oom_full(LOG_DEBUG) +#define log_oom_warning() log_oom_full(LOG_WARNING) bool log_on_console(void) _pure_; @@ -357,6 +361,18 @@ int log_syntax_invalid_utf8_internal( const char *func, const char *rvalue); +int log_syntax_parse_error_internal( + const char *unit, + const char *config_file, + unsigned config_line, + int error, + bool critical, /* When true, propagate the passed error, otherwise this always returns 0. */ + const char *file, + int line, + const char *func, + const char *lvalue, + const char *rvalue); + #define log_syntax(unit, level, config_file, config_line, error, ...) \ ({ \ int _level = (level), _e = (error); \ @@ -373,6 +389,12 @@ int log_syntax_invalid_utf8_internal( : -EINVAL; \ }) +#define log_syntax_parse_error_full(unit, config_file, config_line, error, critical, lvalue, rvalue) \ + log_syntax_parse_error_internal(unit, config_file, config_line, error, critical, PROJECT_FILE, __LINE__, __func__, lvalue, rvalue) + +#define log_syntax_parse_error(unit, config_file, config_line, error, lvalue, rvalue) \ + log_syntax_parse_error_full(unit, config_file, config_line, error, /* critical = */ false, lvalue, rvalue) + #define DEBUG_LOGGING _unlikely_(log_get_max_level() >= LOG_DEBUG) void log_setup(void); @@ -383,7 +405,7 @@ typedef struct LogRateLimit { RateLimit ratelimit; } LogRateLimit; -#define log_ratelimit_internal(_level, _error, _ratelimit, _format, _file, _line, _func, ...) \ +#define log_ratelimit_internal(_level, _error, _ratelimit, _file, _line, _func, _format, ...) \ ({ \ int _log_ratelimit_error = (_error); \ int _log_ratelimit_level = (_level); \ @@ -407,7 +429,7 @@ typedef struct LogRateLimit { ({ \ int _level = (level), _e = (error); \ _e = (log_get_max_level() >= LOG_PRI(_level)) \ - ? log_ratelimit_internal(_level, _e, _ratelimit, format, PROJECT_FILE, __LINE__, __func__, ##__VA_ARGS__) \ + ? log_ratelimit_internal(_level, _e, _ratelimit, PROJECT_FILE, __LINE__, __func__, format, ##__VA_ARGS__) \ : -ERRNO_VALUE(_e); \ _e < 0 ? _e : -ESTRPIPE; \ }) @@ -429,8 +451,8 @@ typedef struct LogRateLimit { #define log_ratelimit_error_errno(error, ...) log_ratelimit_full_errno(LOG_ERR, error, __VA_ARGS__) #define log_ratelimit_emergency_errno(error, ...) log_ratelimit_full_errno(log_emergency_level(), error, __VA_ARGS__) -const char *_log_set_prefix(const char *prefix, bool force); -static inline const char *_log_unset_prefixp(const char **p) { +const char* _log_set_prefix(const char *prefix, bool force); +static inline const char* _log_unset_prefixp(const char **p) { assert(p); _log_set_prefix(*p, true); return NULL; @@ -489,6 +511,15 @@ size_t log_context_num_contexts(void); /* Returns the number of fields in all attached log contexts. */ size_t log_context_num_fields(void); +static inline void _reset_log_level(int *saved_log_level) { + assert(saved_log_level); + + log_set_max_level(*saved_log_level); +} + +#define LOG_CONTEXT_SET_LOG_LEVEL(level) \ + _cleanup_(_reset_log_level) _unused_ int _saved_log_level_ = log_set_max_level(level); + #define LOG_CONTEXT_PUSH(...) \ LOG_CONTEXT_PUSH_STRV(STRV_MAKE(__VA_ARGS__)) diff --git a/src/libnm-systemd-shared/src/basic/macro.h b/src/libnm-systemd-shared/src/basic/macro.h index fe78363b86..026ec13637 100644 --- a/src/libnm-systemd-shared/src/basic/macro.h +++ b/src/libnm-systemd-shared/src/basic/macro.h @@ -30,15 +30,6 @@ #define _function_no_sanitize_float_cast_overflow_ #endif -#if HAVE_WSTRINGOP_TRUNCATION -# define DISABLE_WARNING_STRINGOP_TRUNCATION \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"") -#else -# define DISABLE_WARNING_STRINGOP_TRUNCATION \ - _Pragma("GCC diagnostic push") -#endif - /* test harness */ #define EXIT_TEST_SKIP 77 @@ -211,16 +202,10 @@ static inline int __coverity_check_and_return__(int condition) { #define PTR_TO_UINT64(p) ((uint64_t) ((uintptr_t) (p))) #define UINT64_TO_PTR(u) ((void *) ((uintptr_t) (u))) -#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) -#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) - #define CHAR_TO_STR(x) ((char[2]) { x, 0 }) #define char_array_0(x) x[sizeof(x)-1] = 0; -#define sizeof_field(struct_type, member) sizeof(((struct_type *) 0)->member) -#define endoffsetof_field(struct_type, member) (offsetof(struct_type, member) + sizeof_field(struct_type, member)) - /* Maximum buffer size needed for formatting an unsigned integer type as hex, including space for '0x' * prefix and trailing NUL suffix. */ #define HEXADECIMAL_STR_MAX(type) (2 + sizeof(type) * 2 + 1) @@ -266,15 +251,6 @@ static inline int __coverity_check_and_return__(int condition) { /* Pointers range from NULL to POINTER_MAX */ #define POINTER_MAX ((void*) UINTPTR_MAX) -#define _FOREACH_ARRAY(i, array, num, m, end) \ - for (typeof(array[0]) *i = (array), *end = ({ \ - typeof(num) m = (num); \ - (i && m > 0) ? i + m : NULL; \ - }); end && i < end; i++) - -#define FOREACH_ARRAY(i, array, num) \ - _FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ)) - #define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \ scope type *name##_ref(type *p) { \ if (!p) \ diff --git a/src/libnm-systemd-shared/src/basic/memory-util.h b/src/libnm-systemd-shared/src/basic/memory-util.h index 294aed67df..1f604cc452 100644 --- a/src/libnm-systemd-shared/src/basic/memory-util.h +++ b/src/libnm-systemd-shared/src/basic/memory-util.h @@ -20,7 +20,7 @@ size_t page_size(void) _pure_; #define PAGE_OFFSET_U64(l) ALIGN_OFFSET_U64(l, page_size()) /* Normal memcpy() requires src to be nonnull. We do nothing if n is 0. */ -static inline void *memcpy_safe(void *dst, const void *src, size_t n) { +static inline void* memcpy_safe(void *dst, const void *src, size_t n) { if (n == 0) return dst; assert(src); @@ -28,13 +28,20 @@ static inline void *memcpy_safe(void *dst, const void *src, size_t n) { } /* Normal mempcpy() requires src to be nonnull. We do nothing if n is 0. */ -static inline void *mempcpy_safe(void *dst, const void *src, size_t n) { +static inline void* mempcpy_safe(void *dst, const void *src, size_t n) { if (n == 0) return dst; assert(src); return mempcpy(dst, src, n); } +#define mempcpy_typesafe(dst, src, n) \ + ({ \ + size_t _sz_; \ + assert_se(MUL_SAFE(&_sz_, sizeof((dst)[0]), n)); \ + (typeof((dst)[0])*) mempcpy_safe(dst, src, _sz_); \ + }) + /* Normal memcmp() requires s1 and s2 to be nonnull. We do nothing if n is 0. */ static inline int memcmp_safe(const void *s1, const void *s2, size_t n) { if (n == 0) diff --git a/src/libnm-systemd-shared/src/basic/missing_fcntl.h b/src/libnm-systemd-shared/src/basic/missing_fcntl.h index 24b2dc3119..a6188879c1 100644 --- a/src/libnm-systemd-shared/src/basic/missing_fcntl.h +++ b/src/libnm-systemd-shared/src/basic/missing_fcntl.h @@ -7,6 +7,10 @@ #define F_LINUX_SPECIFIC_BASE 1024 #endif +#ifndef F_DUPFD_QUERY +#define F_DUPFD_QUERY (F_LINUX_SPECIFIC_BASE + 3) +#endif + #ifndef F_SETPIPE_SZ #define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) #endif @@ -69,9 +73,30 @@ /* 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. */ + * it away. Which sucks. Let's define the actual value here, so that we can mask it ourselves. + * + * The precise definition is arch specific, so we use the values defined in the kernel (note that some + * are hexa and others are octal; duplicated as-is from the kernel definitions): + * - alpha, arm, arm64, m68k, mips, parisc, powerpc, sparc: each has a specific value; + * - others: they use the "generic" value (defined in include/uapi/asm-generic/fcntl.h) */ #if O_LARGEFILE != 0 #define RAW_O_LARGEFILE O_LARGEFILE #else -#define RAW_O_LARGEFILE 0100000 +#if defined(__alpha__) || defined(__arm__) || defined(__aarch64__) || defined(__m68k__) +#define RAW_O_LARGEFILE 0400000 +#elif defined(__mips__) +#define RAW_O_LARGEFILE 0x2000 +#elif defined(__parisc__) || defined(__hppa__) +#define RAW_O_LARGEFILE 000004000 +#elif defined(__powerpc__) +#define RAW_O_LARGEFILE 0200000 +#elif defined(__sparc__) +#define RAW_O_LARGEFILE 0x40000 +#else +#define RAW_O_LARGEFILE 00100000 +#endif +#endif + +#ifndef AT_HANDLE_FID +#define AT_HANDLE_FID AT_REMOVEDIR #endif diff --git a/src/libnm-systemd-shared/src/basic/missing_pidfd.h b/src/libnm-systemd-shared/src/basic/missing_pidfd.h new file mode 100644 index 0000000000..4c81514284 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/missing_pidfd.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#if HAVE_PIDFD_OPEN +#include +#endif + +#ifndef PIDFS_IOCTL_MAGIC +# define PIDFS_IOCTL_MAGIC 0xFF +#endif + +#ifndef PIDFD_GET_CGROUP_NAMESPACE +# define PIDFD_GET_CGROUP_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 1) +# define PIDFD_GET_IPC_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 2) +# define PIDFD_GET_MNT_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 3) +# define PIDFD_GET_NET_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 4) +# define PIDFD_GET_PID_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 5) +# define PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 6) +# define PIDFD_GET_TIME_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 7) +# define PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 8) +# define PIDFD_GET_USER_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 9) +# define PIDFD_GET_UTS_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 10) +#endif + +#ifndef PIDFD_GET_INFO +struct pidfd_info { + __u64 mask; + __u64 cgroupid; + __u32 pid; + __u32 tgid; + __u32 ppid; + __u32 ruid; + __u32 rgid; + __u32 euid; + __u32 egid; + __u32 suid; + __u32 sgid; + __u32 fsuid; + __u32 fsgid; + __u32 spare0[1]; +}; + +#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info) +#define PIDFD_INFO_PID (1UL << 0) +#define PIDFD_INFO_CREDS (1UL << 1) +#define PIDFD_INFO_CGROUPID (1UL << 2) +#endif diff --git a/src/libnm-systemd-shared/src/basic/missing_random.h b/src/libnm-systemd-shared/src/basic/missing_random.h index 443b913685..5f40c4e58c 100644 --- a/src/libnm-systemd-shared/src/basic/missing_random.h +++ b/src/libnm-systemd-shared/src/basic/missing_random.h @@ -1,20 +1,28 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#if USE_SYS_RANDOM_H +#include "macro.h" + +#if HAVE_GETRANDOM # include #else # include #endif #ifndef GRND_NONBLOCK -#define GRND_NONBLOCK 0x0001 +# define GRND_NONBLOCK 0x0001 +#else +assert_cc(GRND_NONBLOCK == 0x0001); #endif #ifndef GRND_RANDOM -#define GRND_RANDOM 0x0002 +# define GRND_RANDOM 0x0002 +#else +assert_cc(GRND_RANDOM == 0x0002); #endif #ifndef GRND_INSECURE -#define GRND_INSECURE 0x0004 +# define GRND_INSECURE 0x0004 +#else +assert_cc(GRND_INSECURE == 0x0004); #endif diff --git a/src/libnm-systemd-shared/src/basic/missing_socket.h b/src/libnm-systemd-shared/src/basic/missing_socket.h index 47cc7626aa..8460ce13bf 100644 --- a/src/libnm-systemd-shared/src/basic/missing_socket.h +++ b/src/libnm-systemd-shared/src/basic/missing_socket.h @@ -3,42 +3,6 @@ #include -#if HAVE_LINUX_VM_SOCKETS_H -#include -#else -struct sockaddr_vm { - unsigned short svm_family; - unsigned short svm_reserved1; - unsigned int svm_port; - unsigned int svm_cid; - unsigned char svm_zero[sizeof(struct sockaddr) - - sizeof(unsigned short) - - sizeof(unsigned short) - - sizeof(unsigned int) - - sizeof(unsigned int)]; -}; -#endif /* !HAVE_LINUX_VM_SOCKETS_H */ - -#ifndef VMADDR_CID_ANY -#define VMADDR_CID_ANY -1U -#endif - -#ifndef VMADDR_CID_HYPERVISOR -#define VMADDR_CID_HYPERVISOR 0U -#endif - -#ifndef VMADDR_CID_LOCAL -#define VMADDR_CID_LOCAL 1U -#endif - -#ifndef VMADDR_CID_HOST -#define VMADDR_CID_HOST 2U -#endif - -#ifndef VMADDR_PORT_ANY -#define VMADDR_PORT_ANY -1U -#endif - #ifndef AF_VSOCK #define AF_VSOCK 40 #endif @@ -51,6 +15,10 @@ struct sockaddr_vm { #define SO_PEERGROUPS 59 #endif +#ifndef SO_PASSPIDFD +#define SO_PASSPIDFD 76 +#endif + #ifndef SO_PEERPIDFD #define SO_PEERPIDFD 77 #endif @@ -72,11 +40,14 @@ struct sockaddr_vm { #define SOL_SCTP 132 #endif -/* Not exposed yet. Defined in include/linux/socket.h */ #ifndef SCM_SECURITY #define SCM_SECURITY 0x03 #endif +#ifndef SCM_PIDFD +#define SCM_PIDFD 0x04 +#endif + /* netinet/in.h */ #ifndef IP_FREEBIND #define IP_FREEBIND 15 @@ -98,7 +69,9 @@ struct sockaddr_vm { #define IPV6_RECVFRAGSIZE 77 #endif -/* linux/sockios.h */ -#ifndef SIOCGSKNS -#define SIOCGSKNS 0x894C +/* The maximum number of fds that SCM_RIGHTS accepts. This is an internal kernel constant, but very much + * useful for userspace too. It's documented in unix(7) these days, hence should be fairly reliable to define + * here. */ +#ifndef SCM_MAX_FD +#define SCM_MAX_FD 253U #endif diff --git a/src/libnm-systemd-shared/src/basic/missing_syscall.h b/src/libnm-systemd-shared/src/basic/missing_syscall.h index d795efd8f2..37048e1bc0 100644 --- a/src/libnm-systemd-shared/src/basic/missing_syscall.h +++ b/src/libnm-systemd-shared/src/basic/missing_syscall.h @@ -22,6 +22,7 @@ #include "macro.h" #include "missing_keyctl.h" +#include "missing_sched.h" #include "missing_stat.h" #include "missing_syscall_def.h" @@ -79,12 +80,7 @@ static inline int missing_ioprio_set(int which, int who, int ioprio) { #if !HAVE_MEMFD_CREATE static inline int missing_memfd_create(const char *name, unsigned int flags) { -# ifdef __NR_memfd_create return syscall(__NR_memfd_create, name, flags); -# else - errno = ENOSYS; - return -1; -# endif } # define memfd_create missing_memfd_create @@ -95,12 +91,7 @@ static inline int missing_memfd_create(const char *name, unsigned int flags) { #if !HAVE_GETRANDOM /* glibc says getrandom() returns ssize_t */ static inline ssize_t missing_getrandom(void *buffer, size_t count, unsigned flags) { -# ifdef __NR_getrandom return syscall(__NR_getrandom, buffer, count, flags); -# else - errno = ENOSYS; - return -1; -# endif } # define getrandom missing_getrandom @@ -146,12 +137,7 @@ static inline int missing_name_to_handle_at(int fd, const char *name, struct fil #if !HAVE_SETNS static inline int missing_setns(int fd, int nstype) { -# ifdef __NR_setns return syscall(__NR_setns, fd, nstype); -# else - errno = ENOSYS; - return -1; -# endif } # define setns missing_setns @@ -171,12 +157,7 @@ static inline pid_t raw_getpid(void) { #if !HAVE_RENAMEAT2 static inline int missing_renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) { -# ifdef __NR_renameat2 return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags); -# else - errno = ENOSYS; - return -1; -# endif } # define renameat2 missing_renameat2 @@ -186,12 +167,7 @@ static inline int missing_renameat2(int oldfd, const char *oldname, int newfd, c #if !HAVE_KCMP static inline int missing_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) { -# if defined __NR_kcmp && __NR_kcmp >= 0 return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2); -# else - errno = ENOSYS; - return -1; -# endif } # define kcmp missing_kcmp @@ -201,34 +177,19 @@ static inline int missing_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long i #if !HAVE_KEYCTL static inline long missing_keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { -# if defined __NR_keyctl && __NR_keyctl >= 0 return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); -# else - errno = ENOSYS; - return -1; -# endif # define keyctl missing_keyctl } static inline key_serial_t missing_add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) { -# if defined __NR_add_key && __NR_add_key >= 0 return syscall(__NR_add_key, type, description, payload, plen, ringid); -# else - errno = ENOSYS; - return -1; -# endif # define add_key missing_add_key } static inline key_serial_t missing_request_key(const char *type, const char *description, const char * callout_info, key_serial_t destringid) { -# if defined __NR_request_key && __NR_request_key >= 0 return syscall(__NR_request_key, type, description, callout_info, destringid); -# else - errno = ENOSYS; - return -1; -# endif # define request_key missing_request_key } @@ -338,12 +299,7 @@ static inline long missing_get_mempolicy(int *mode, unsigned long *nodemask, #if !HAVE_PIDFD_SEND_SIGNAL static inline int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags) { -# ifdef __NR_pidfd_send_signal return syscall(__NR_pidfd_send_signal, fd, sig, info, flags); -# else - errno = ENOSYS; - return -1; -# endif } # define pidfd_send_signal missing_pidfd_send_signal @@ -351,12 +307,7 @@ static inline int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, un #if !HAVE_PIDFD_OPEN static inline int missing_pidfd_open(pid_t pid, unsigned flags) { -# ifdef __NR_pidfd_open return syscall(__NR_pidfd_open, pid, flags); -# else - errno = ENOSYS; - return -1; -# endif } # define pidfd_open missing_pidfd_open @@ -412,23 +363,14 @@ static inline int missing_execveat(int dirfd, const char *pathname, /* ======================================================================= */ #if !HAVE_CLOSE_RANGE -static inline int missing_close_range(int first_fd, int end_fd, unsigned flags) { +static inline int missing_close_range(unsigned first_fd, unsigned end_fd, unsigned flags) { # ifdef __NR_close_range /* Kernel-side the syscall expects fds as unsigned integers (just like close() actually), while - * userspace exclusively uses signed integers for fds. We don't know just yet how glibc is going to - * wrap this syscall, but let's assume it's going to be similar to what they do for close(), - * i.e. make the same unsigned → signed type change from the raw kernel syscall compared to the - * userspace wrapper. There's only one caveat for this: unlike for close() there's the special - * UINT_MAX fd value for the 'end_fd' argument. Let's safely map that to -1 here. And let's refuse - * any other negative values. */ - if ((first_fd < 0) || (end_fd < 0 && end_fd != -1)) { - errno = -EBADF; - return -1; - } - + * userspace exclusively uses signed integers for fds. glibc chose to expose it 1:1 however, hence we + * do so here too, even if we end up passing signed fds to it most of the time. */ return syscall(__NR_close_range, - (unsigned) first_fd, - end_fd == -1 ? UINT_MAX : (unsigned) end_fd, /* Of course, the compiler should figure out that this is the identity mapping IRL */ + first_fd, + end_fd, flags); # else errno = ENOSYS; @@ -676,6 +618,17 @@ static inline ssize_t missing_getdents64(int fd, void *buffer, size_t length) { /* ======================================================================= */ +#if !HAVE_SCHED_SETATTR + +static inline ssize_t missing_sched_setattr(pid_t pid, struct sched_attr *attr, unsigned int flags) { + return syscall(__NR_sched_setattr, pid, attr, flags); +} + +# define sched_setattr missing_sched_setattr +#endif + +/* ======================================================================= */ + /* glibc does not provide clone() on ia64, only clone2(). Not only that, but it also doesn't provide a * prototype, only the symbol in the shared library (it provides a prototype for clone(), but not the * symbol in the shared library). */ @@ -687,3 +640,19 @@ int __clone2(int (*fn)(void *), void *stack_base, size_t stack_size, int flags, * at build time) and just define it. Once the kernel drops ia64 support, we can drop this too. */ #define HAVE_CLONE 1 #endif + +/* ======================================================================= */ + +#if !HAVE_QUOTACTL_FD + +static inline int missing_quotactl_fd(int fd, int cmd, int id, void *addr) { +#if defined __NR_quotactl_fd + return syscall(__NR_quotactl_fd, fd, cmd, id, addr); +#else + errno = ENOSYS; + return -1; +#endif +} + +# define quotactl_fd missing_quotactl_fd +#endif diff --git a/src/libnm-systemd-shared/src/basic/missing_threads.h b/src/libnm-systemd-shared/src/basic/missing_threads.h index fb3b72249b..c7da1dbd5e 100644 --- a/src/libnm-systemd-shared/src/basic/missing_threads.h +++ b/src/libnm-systemd-shared/src/basic/missing_threads.h @@ -5,9 +5,7 @@ #if HAVE_THREADS_H # include #elif !(defined(thread_local)) -/* Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__ - * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769 */ -# if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16)) +# ifndef __STDC_NO_THREADS__ # define thread_local _Thread_local # else # define thread_local __thread diff --git a/src/libnm-systemd-shared/src/basic/missing_type.h b/src/libnm-systemd-shared/src/basic/missing_type.h index f6233090a9..1d17705c35 100644 --- a/src/libnm-systemd-shared/src/basic/missing_type.h +++ b/src/libnm-systemd-shared/src/basic/missing_type.h @@ -4,9 +4,9 @@ #include #if !HAVE_CHAR32_T -#define char32_t uint32_t +# define char32_t uint32_t #endif #if !HAVE_CHAR16_T -#define char16_t uint16_t +# define char16_t uint16_t #endif diff --git a/src/libnm-systemd-shared/src/basic/missing_wait.h b/src/libnm-systemd-shared/src/basic/missing_wait.h new file mode 100644 index 0000000000..3965b5bdbf --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/missing_wait.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "macro.h" + +#ifndef P_PIDFD +# define P_PIDFD 3 +#else +assert_cc(P_PIDFD == 3); +#endif diff --git a/src/libnm-systemd-shared/src/basic/namespace-util.h b/src/libnm-systemd-shared/src/basic/namespace-util.h index d1d015612f..3d40a515e7 100644 --- a/src/libnm-systemd-shared/src/basic/namespace-util.h +++ b/src/libnm-systemd-shared/src/basic/namespace-util.h @@ -3,6 +3,8 @@ #include +#include "pidref.h" + typedef enum NamespaceType { NAMESPACE_CGROUP, NAMESPACE_IPC, @@ -19,9 +21,23 @@ typedef enum NamespaceType { extern const struct namespace_info { const char *proc_name; const char *proc_path; - unsigned int clone_flag; + unsigned long clone_flag; + unsigned long pidfd_get_ns_ioctl_cmd; + ino_t root_inode; } namespace_info[_NAMESPACE_TYPE_MAX + 1]; +NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag); + +int pidref_namespace_open_by_type(const PidRef *pidref, NamespaceType type); +int namespace_open_by_type(NamespaceType type); + +int pidref_namespace_open( + const PidRef *pidref, + int *ret_pidns_fd, + int *ret_mntns_fd, + int *ret_netns_fd, + int *ret_userns_fd, + int *ret_root_fd); int namespace_open( pid_t pid, int *ret_pidns_fd, @@ -29,11 +45,28 @@ int namespace_open( int *ret_netns_fd, int *ret_userns_fd, int *ret_root_fd); + int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd); -int fd_is_ns(int fd, unsigned long nsflag); +int fd_is_namespace(int fd, NamespaceType type); +int is_our_namespace(int fd, NamespaceType type); + +int namespace_is_init(NamespaceType type); + +int pidref_in_same_namespace(PidRef *pid1, PidRef *pid2, NamespaceType type); +static inline int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type) { + assert(pid1 >= 0); + assert(pid2 >= 0); + return pidref_in_same_namespace(pid1 == 0 ? NULL : &PIDREF_MAKE_FROM_PID(pid1), + pid2 == 0 ? NULL : &PIDREF_MAKE_FROM_PID(pid2), + type); +} + +int namespace_get_leader(PidRef *pidref, NamespaceType type, PidRef *ret); int detach_mount_namespace(void); +int detach_mount_namespace_harder(uid_t target_uid, gid_t target_gid); +int detach_mount_namespace_userns(int userns_fd); static inline bool userns_shift_range_valid(uid_t shift, uid_t range) { /* Checks that the specified userns range makes sense, i.e. contains at least one UID, and the end @@ -50,6 +83,16 @@ static inline bool userns_shift_range_valid(uid_t shift, uid_t range) { return true; } +int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_range); + +int userns_acquire_empty(void); int userns_acquire(const char *uid_map, const char *gid_map); +int userns_enter_and_pin(int userns_fd, pid_t *ret_pid); + +int userns_get_base_uid(int userns_fd, uid_t *ret_uid, gid_t *ret_gid); + +int process_is_owned_by_uid(const PidRef *pidref, uid_t uid); + +int is_idmapping_supported(const char *path); + int netns_acquire(void); -int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type); diff --git a/src/libnm-systemd-shared/src/basic/parse-util.c b/src/libnm-systemd-shared/src/basic/parse-util.c index 5971173915..faa5344921 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.c +++ b/src/libnm-systemd-shared/src/basic/parse-util.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -633,7 +634,7 @@ int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) { s = *p; /* accept any number of digits, strtoull is limited to 19 */ - for (size_t i = 0; i < digits; i++,s++) { + for (size_t i = 0; i < digits; i++, s++) { if (!ascii_isdigit(*s)) { if (i == 0) return -EINVAL; @@ -714,22 +715,6 @@ int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow return 0; } -int parse_ip_prefix_length(const char *s, int *ret) { - unsigned l; - int r; - - r = safe_atou(s, &l); - if (r < 0) - return r; - - if (l > 128) - return -ERANGE; - - *ret = (int) l; - - return 0; -} - int parse_oom_score_adjust(const char *s, int *ret) { int r, v; diff --git a/src/libnm-systemd-shared/src/basic/parse-util.h b/src/libnm-systemd-shared/src/basic/parse-util.h index c12988ef20..a47c8c7935 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.h +++ b/src/libnm-systemd-shared/src/basic/parse-util.h @@ -141,8 +141,6 @@ int parse_nice(const char *p, int *ret); int parse_ip_port(const char *s, uint16_t *ret); int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow_zero); -int parse_ip_prefix_length(const char *s, int *ret); - int parse_oom_score_adjust(const char *s, int *ret); /* Implement floating point using fixed integers, to improve performance when diff --git a/src/libnm-systemd-shared/src/basic/path-util.c b/src/libnm-systemd-shared/src/basic/path-util.c index 6810bf66aa..78ba10ed80 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.c +++ b/src/libnm-systemd-shared/src/basic/path-util.c @@ -217,8 +217,10 @@ int path_make_relative_parent(const char *from_child, const char *to, char **ret return path_make_relative(from, to, ret); } -char* path_startswith_strv(const char *p, char **set) { - STRV_FOREACH(s, set) { +char* path_startswith_strv(const char *p, char * const *strv) { + assert(p); + + STRV_FOREACH(s, strv) { char *t; t = path_startswith(p, *s); @@ -525,6 +527,18 @@ int path_compare_filename(const char *a, const char *b) { return strcmp(fa, fb); } +int path_equal_or_inode_same_full(const char *a, const char *b, int flags) { + /* Returns true if paths are of the same entry, false if not, <0 on error. */ + + if (path_equal(a, b)) + return 1; + + if (!a || !b) + return 0; + + return inode_same(a, b, flags); +} + char* path_extend_internal(char **x, ...) { size_t sz, old_sz; char *q, *nx; @@ -639,7 +653,7 @@ static int find_executable_impl(const char *name, const char *root, char **ret_f * /usr/bin/sleep when find_executables is called. Hence, this function should be invoked when * needed to avoid unforeseen regression or other complicated changes. */ if (root) { - /* prefix root to name in case full paths are not specified */ + /* prefix root to name in case full paths are not specified */ r = chase(name, root, CHASE_PREFIX_ROOT, &path_name, /* ret_fd= */ NULL); if (r < 0) return r; @@ -655,6 +669,8 @@ static int find_executable_impl(const char *name, const char *root, char **ret_f r = path_make_absolute_cwd(name, ret_filename); if (r < 0) return r; + + path_simplify(*ret_filename); } if (ret_fd) @@ -666,48 +682,49 @@ static int find_executable_impl(const char *name, const char *root, char **ret_f int find_executable_full( const char *name, const char *root, - char **exec_search_path, + char * const *exec_search_path, bool use_path_envvar, char **ret_filename, int *ret_fd) { int last_error = -ENOENT, r = 0; - const char *p = NULL; assert(name); if (is_path(name)) return find_executable_impl(name, root, ret_filename, ret_fd); - if (use_path_envvar) - /* Plain getenv, not secure_getenv, because we want to actually allow the user to pick the - * binary. */ - p = getenv("PATH"); - if (!p) - p = DEFAULT_PATH; - if (exec_search_path) { STRV_FOREACH(element, exec_search_path) { _cleanup_free_ char *full_path = NULL; - if (!path_is_absolute(*element)) + if (!path_is_absolute(*element)) { + log_debug("Exec search path '%s' isn't absolute, ignoring.", *element); continue; + } full_path = path_join(*element, name); if (!full_path) return -ENOMEM; r = find_executable_impl(full_path, root, ret_filename, ret_fd); - if (r < 0) { - if (r != -EACCES) - last_error = r; - continue; - } - return 0; + if (r >= 0) + return 0; + if (r != -EACCES) + last_error = r; } return last_error; } + const char *p = NULL; + + if (use_path_envvar) + /* Plain getenv, not secure_getenv, because we want to actually allow the user to pick the + * binary. */ + p = getenv("PATH"); + if (!p) + p = default_PATH(); + /* Resolve a single-component name to a full path */ for (;;) { _cleanup_free_ char *element = NULL; @@ -718,22 +735,20 @@ int find_executable_full( if (r == 0) break; - if (!path_is_absolute(element)) + if (!path_is_absolute(element)) { + log_debug("Exec search path '%s' isn't absolute, ignoring.", element); continue; + } if (!path_extend(&element, name)) return -ENOMEM; r = find_executable_impl(element, root, ret_filename, ret_fd); - if (r < 0) { - /* PATH entries which we don't have access to are ignored, as per tradition. */ - if (r != -EACCES) - last_error = r; - continue; - } - - /* Found it! */ - return 0; + if (r >= 0) /* Found it! */ + return 0; + /* PATH entries which we don't have access to are ignored, as per tradition. */ + if (r != -EACCES) + last_error = r; } return last_error; @@ -1094,7 +1109,6 @@ int path_extract_filename(const char *path, char **ret) { } int path_extract_directory(const char *path, char **ret) { - _cleanup_free_ char *a = NULL; const char *c, *next = NULL; int r; @@ -1118,14 +1132,10 @@ int path_extract_directory(const char *path, char **ret) { if (*path != '/') /* filename only */ return -EDESTADDRREQ; - a = strdup("/"); - if (!a) - return -ENOMEM; - *ret = TAKE_PTR(a); - return 0; + return strdup_to(ret, "/"); } - a = strndup(path, next - path); + _cleanup_free_ char *a = strndup(path, next - path); if (!a) return -ENOMEM; @@ -1336,6 +1346,20 @@ bool dot_or_dot_dot(const char *path) { return path[2] == 0; } +bool path_implies_directory(const char *path) { + + /* Sometimes, if we look at a path we already know it must refer to a directory, because it is + * suffixed with a slash, or its last component is "." or ".." */ + + if (!path) + return false; + + if (dot_or_dot_dot(path)) + return true; + + return ENDSWITH_SET(path, "/", "/.", "/.."); +} + bool empty_or_root(const char *path) { /* For operations relative to some root directory, returns true if the specified root directory is @@ -1347,7 +1371,9 @@ bool empty_or_root(const char *path) { return path_equal(path, "/"); } -bool path_strv_contains(char **l, const char *path) { +bool path_strv_contains(char * const *l, const char *path) { + assert(path); + STRV_FOREACH(i, l) if (path_equal(*i, path)) return true; @@ -1355,7 +1381,9 @@ bool path_strv_contains(char **l, const char *path) { return false; } -bool prefixed_path_strv_contains(char **l, const char *path) { +bool prefixed_path_strv_contains(char * const *l, const char *path) { + assert(path); + STRV_FOREACH(i, l) { const char *j = *i; @@ -1363,6 +1391,7 @@ bool prefixed_path_strv_contains(char **l, const char *path) { j++; if (*j == '+') j++; + if (path_equal(j, path)) return true; } @@ -1432,3 +1461,31 @@ int path_glob_can_match(const char *pattern, const char *prefix, char **ret) { *ret = NULL; return false; } + +const char* default_PATH(void) { +#if HAVE_SPLIT_BIN + static int split = -1; + int r; + + /* Check whether /usr/sbin is not a symlink and return the appropriate $PATH. + * On error fall back to the safe value with both directories as configured… */ + + if (split < 0) + STRV_FOREACH_PAIR(bin, sbin, STRV_MAKE("/usr/bin", "/usr/sbin", + "/usr/local/bin", "/usr/local/sbin")) { + r = inode_same(*bin, *sbin, AT_NO_AUTOMOUNT); + if (r > 0 || r == -ENOENT) + continue; + if (r < 0) + log_debug_errno(r, "Failed to compare \"%s\" and \"%s\", using compat $PATH: %m", + *bin, *sbin); + split = true; + break; + } + if (split < 0) + split = false; + if (split) + return DEFAULT_PATH_WITH_SBIN; +#endif + return DEFAULT_PATH_WITHOUT_SBIN; +} diff --git a/src/libnm-systemd-shared/src/basic/path-util.h b/src/libnm-systemd-shared/src/basic/path-util.h index 39b6714525..dff5a3a549 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.h +++ b/src/libnm-systemd-shared/src/basic/path-util.h @@ -11,27 +11,26 @@ #include "strv.h" #include "time-util.h" -#define PATH_SPLIT_SBIN_BIN(x) x "sbin:" x "bin" -#define PATH_SPLIT_SBIN_BIN_NULSTR(x) x "sbin\0" x "bin\0" +#define PATH_SPLIT_BIN(x) x "sbin:" x "bin" +#define PATH_SPLIT_BIN_NULSTR(x) x "sbin\0" x "bin\0" -#define PATH_NORMAL_SBIN_BIN(x) x "bin" -#define PATH_NORMAL_SBIN_BIN_NULSTR(x) x "bin\0" +#define PATH_MERGED_BIN(x) x "bin" +#define PATH_MERGED_BIN_NULSTR(x) x "bin\0" -#if HAVE_SPLIT_BIN -# define PATH_SBIN_BIN(x) PATH_SPLIT_SBIN_BIN(x) -# define PATH_SBIN_BIN_NULSTR(x) PATH_SPLIT_SBIN_BIN_NULSTR(x) +#define DEFAULT_PATH_WITH_SBIN PATH_SPLIT_BIN("/usr/local/") ":" PATH_SPLIT_BIN("/usr/") +#define DEFAULT_PATH_WITHOUT_SBIN PATH_MERGED_BIN("/usr/local/") ":" PATH_MERGED_BIN("/usr/") + +#define DEFAULT_PATH_COMPAT PATH_SPLIT_BIN("/usr/local/") ":" PATH_SPLIT_BIN("/usr/") ":" PATH_SPLIT_BIN("/") + +const char* default_PATH(void); + +static inline const char* default_user_PATH(void) { +#ifdef DEFAULT_USER_PATH + return DEFAULT_USER_PATH; #else -# define PATH_SBIN_BIN(x) PATH_NORMAL_SBIN_BIN(x) -# define PATH_SBIN_BIN_NULSTR(x) PATH_NORMAL_SBIN_BIN_NULSTR(x) -#endif - -#define DEFAULT_PATH PATH_SBIN_BIN("/usr/local/") ":" PATH_SBIN_BIN("/usr/") -#define DEFAULT_PATH_NULSTR PATH_SBIN_BIN_NULSTR("/usr/local/") PATH_SBIN_BIN_NULSTR("/usr/") -#define DEFAULT_PATH_COMPAT PATH_SPLIT_SBIN_BIN("/usr/local/") ":" PATH_SPLIT_SBIN_BIN("/usr/") ":" PATH_SPLIT_SBIN_BIN("/") - -#ifndef DEFAULT_USER_PATH -# define DEFAULT_USER_PATH DEFAULT_PATH + return default_PATH(); #endif +} static inline bool is_path(const char *p) { if (!p) /* A NULL pointer is definitely not a path */ @@ -68,14 +67,19 @@ static inline bool path_equal_filename(const char *a, const char *b) { return path_compare_filename(a, b) == 0; } +int path_equal_or_inode_same_full(const char *a, const char *b, int flags); static inline bool path_equal_or_inode_same(const char *a, const char *b, int flags) { - return path_equal(a, b) || inode_same(a, b, flags) > 0; + return path_equal_or_inode_same_full(a, b, flags) > 0; } char* path_extend_internal(char **x, ...); #define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX) #define path_join(...) path_extend_internal(NULL, __VA_ARGS__, POINTER_MAX) +static inline char* skip_leading_slash(const char *p) { + return skip_leading_chars(p, "/"); +} + typedef enum PathSimplifyFlags { PATH_SIMPLIFY_KEEP_TRAILING_SLASH = 1 << 0, } PathSimplifyFlags; @@ -101,21 +105,23 @@ static inline int path_simplify_alloc(const char *path, char **ret) { return 0; } -static inline bool path_equal_ptr(const char *a, const char *b) { - return !!a == !!b && (!a || path_equal(a, b)); -} - /* Note: the search terminates on the first NULL item. */ #define PATH_IN_SET(p, ...) path_strv_contains(STRV_MAKE(__VA_ARGS__), p) -char* path_startswith_strv(const char *p, char **set); +char* path_startswith_strv(const char *p, char * const *strv); #define PATH_STARTSWITH_SET(p, ...) path_startswith_strv(p, STRV_MAKE(__VA_ARGS__)) int path_strv_make_absolute_cwd(char **l); char** path_strv_resolve(char **l, const char *root); char** path_strv_resolve_uniq(char **l, const char *root); -int find_executable_full(const char *name, const char *root, char **exec_search_path, bool use_path_envvar, char **ret_filename, int *ret_fd); +int find_executable_full( + const char *name, + const char *root, + char * const *exec_search_path, + bool use_path_envvar, + char **ret_filename, + int *ret_fd); static inline int find_executable(const char *name, char **ret_filename) { return find_executable_full(name, /* root= */ NULL, NULL, true, ret_filename, NULL); } @@ -201,7 +207,9 @@ bool valid_device_allow_pattern(const char *path); bool dot_or_dot_dot(const char *path); -static inline const char *skip_dev_prefix(const char *p) { +bool path_implies_directory(const char *path); + +static inline const char* skip_dev_prefix(const char *p) { const char *e; /* Drop any /dev prefix if there is any */ @@ -216,7 +224,7 @@ static inline const char* empty_to_root(const char *path) { return isempty(path) ? "/" : path; } -bool path_strv_contains(char **l, const char *path); -bool prefixed_path_strv_contains(char **l, const char *path); +bool path_strv_contains(char * const *l, const char *path); +bool prefixed_path_strv_contains(char * const *l, const char *path); int path_glob_can_match(const char *pattern, const char *prefix, char **ret); diff --git a/src/libnm-systemd-shared/src/basic/pidfd-util.c b/src/libnm-systemd-shared/src/basic/pidfd-util.c new file mode 100644 index 0000000000..36fe224324 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/pidfd-util.c @@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "macro.h" +#include "memory-util.h" +#include "missing_magic.h" +#include "missing_threads.h" +#include "parse-util.h" +#include "path-util.h" +#include "pidfd-util.h" +#include "process-util.h" +#include "stat-util.h" +#include "string-util.h" + +static int have_pidfs = -1; + +static int pidfd_check_pidfs(int pid_fd) { + + /* NB: the passed fd *must* be acquired via pidfd_open(), i.e. must be a true pidfd! */ + + if (have_pidfs >= 0) + return have_pidfs; + + return (have_pidfs = fd_is_fs_type(pid_fd, PID_FS_MAGIC)); +} + +int pidfd_get_namespace(int fd, unsigned long ns_type_cmd) { + static bool cached_supported = true; + + /* Obtain the namespace fd from pidfd directly through ioctl(PIDFD_GET_*_NAMESPACE). + * + * Returns -EOPNOTSUPP if ioctl on pidfds are not supported, -ENOPKG if the requested namespace + * is disabled in kernel. (The errno used are different from what kernel returns via ioctl(), + * see below) */ + + assert(fd >= 0); + + /* If we know ahead of time that pidfs is unavailable, shortcut things. But otherwise we don't + * call pidfd_check_pidfs() here, which is kinda extraneous and our own cache is required + * anyways (pidfs is introduced in kernel 6.9 while ioctl support there is added in 6.11). */ + if (have_pidfs == 0 || !cached_supported) + return -EOPNOTSUPP; + + int nsfd = ioctl(fd, ns_type_cmd); + if (nsfd < 0) { + /* Kernel returns EOPNOTSUPP if the ns type in question is disabled. Hence we need to look + * at precise errno instead of generic ERRNO_IS_(IOCTL_)NOT_SUPPORTED. */ + if (IN_SET(errno, ENOTTY, EINVAL)) { + cached_supported = false; + return -EOPNOTSUPP; + } + if (errno == EOPNOTSUPP) /* Translate to something more recognizable */ + return -ENOPKG; + + return -errno; + } + + return nsfd; +} + +static int pidfd_get_info(int fd, struct pidfd_info *info) { + static bool cached_supported = true; + + assert(fd >= 0); + assert(info); + + if (have_pidfs == 0 || !cached_supported) + return -EOPNOTSUPP; + + if (ioctl(fd, PIDFD_GET_INFO, info) < 0) { + if (ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) { + cached_supported = false; + return -EOPNOTSUPP; + } + + return -errno; + } + + return 0; +} + +static int pidfd_get_pid_fdinfo(int fd, pid_t *ret) { + char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; + _cleanup_free_ char *fdinfo = NULL; + int r; + + assert(fd >= 0); + + xsprintf(path, "/proc/self/fdinfo/%i", fd); + + r = read_full_virtual_file(path, &fdinfo, NULL); + if (r == -ENOENT) + return proc_fd_enoent_errno(); + if (r < 0) + return r; + + char *p = find_line_startswith(fdinfo, "Pid:"); + if (!p) + return -ENOTTY; /* not a pidfd? */ + + p = skip_leading_chars(p, /* bad = */ NULL); + p[strcspn(p, WHITESPACE)] = 0; + + if (streq(p, "0")) + return -EREMOTE; /* PID is in foreign PID namespace? */ + if (streq(p, "-1")) + return -ESRCH; /* refers to reaped process? */ + + return parse_pid(p, ret); +} + +static int pidfd_get_pid_ioctl(int fd, pid_t *ret) { + struct pidfd_info info = { .mask = PIDFD_INFO_PID }; + int r; + + assert(fd >= 0); + + r = pidfd_get_info(fd, &info); + if (r < 0) + return r; + + assert(FLAGS_SET(info.mask, PIDFD_INFO_PID)); + + if (ret) + *ret = info.pid; + return 0; +} + +int pidfd_get_pid(int fd, pid_t *ret) { + int r; + + /* Converts a pidfd into a pid. We try ioctl(PIDFD_GET_INFO) (kernel 6.13+) first, + * /proc/self/fdinfo/ as fallback. Well known errors: + * + * -EBADF → fd invalid + * -ESRCH → fd valid, but process is already reaped + * + * pidfd_get_pid_fdinfo() might additionally fail for other reasons: + * + * -ENOSYS → /proc/ not mounted + * -ENOTTY → fd valid, but not a pidfd + * -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one + * (when using PIDFD_GET_INFO this is indistinguishable from -ESRCH) + */ + + assert(fd >= 0); + + r = pidfd_get_pid_ioctl(fd, ret); + if (r != -EOPNOTSUPP) + return r; + + return pidfd_get_pid_fdinfo(fd, ret); +} + +int pidfd_verify_pid(int pidfd, pid_t pid) { + pid_t current_pid; + int r; + + assert(pidfd >= 0); + assert(pid > 0); + + r = pidfd_get_pid(pidfd, ¤t_pid); + if (r < 0) + return r; + + return current_pid != pid ? -ESRCH : 0; +} + +int pidfd_get_ppid(int fd, pid_t *ret) { + struct pidfd_info info = { .mask = PIDFD_INFO_PID }; + int r; + + assert(fd >= 0); + + r = pidfd_get_info(fd, &info); + if (r < 0) + return r; + + assert(FLAGS_SET(info.mask, PIDFD_INFO_PID)); + + if (info.ppid == 0) /* See comments in pid_get_ppid() */ + return -EADDRNOTAVAIL; + + if (ret) + *ret = info.ppid; + return 0; +} + +int pidfd_get_uid(int fd, uid_t *ret) { + struct pidfd_info info = { .mask = PIDFD_INFO_CREDS }; + int r; + + assert(fd >= 0); + + r = pidfd_get_info(fd, &info); + if (r < 0) + return r; + + assert(FLAGS_SET(info.mask, PIDFD_INFO_CREDS)); + + if (ret) + *ret = info.ruid; + return 0; +} + +int pidfd_get_cgroupid(int fd, uint64_t *ret) { + struct pidfd_info info = { .mask = PIDFD_INFO_CGROUPID }; + int r; + + assert(fd >= 0); + + r = pidfd_get_info(fd, &info); + if (r < 0) + return r; + + assert(FLAGS_SET(info.mask, PIDFD_INFO_CGROUPID)); + + if (ret) + *ret = info.cgroupid; + return 0; +} + +int pidfd_get_inode_id(int fd, uint64_t *ret) { + int r; + + assert(fd >= 0); + + r = pidfd_check_pidfs(fd); + if (r < 0) + return r; + if (r == 0) + return -EOPNOTSUPP; + + struct stat st; + if (fstat(fd, &st) < 0) + return -errno; + + if (ret) + *ret = (uint64_t) st.st_ino; + return 0; +} + +int pidfd_get_inode_id_self_cached(uint64_t *ret) { + static thread_local uint64_t cached = 0; + static thread_local pid_t initialized = 0; /* < 0: cached error; == 0: invalid; > 0: valid and pid that was current */ + int r; + + assert(ret); + + if (initialized == getpid_cached()) { + *ret = cached; + return 0; + } + if (initialized < 0) + return initialized; + + _cleanup_close_ int fd = pidfd_open(getpid_cached(), 0); + if (fd < 0) + return -errno; + + r = pidfd_get_inode_id(fd, &cached); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return (initialized = -EOPNOTSUPP); + if (r < 0) + return r; + + *ret = cached; + initialized = getpid_cached(); + return 0; +} diff --git a/src/libnm-systemd-shared/src/basic/pidfd-util.h b/src/libnm-systemd-shared/src/basic/pidfd-util.h new file mode 100644 index 0000000000..c20de6df67 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/pidfd-util.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#include "missing_pidfd.h" +#include "missing_syscall.h" + +int pidfd_get_namespace(int fd, unsigned long ns_type_cmd); + +int pidfd_get_pid(int fd, pid_t *ret); +int pidfd_verify_pid(int pidfd, pid_t pid); + +int pidfd_get_ppid(int fd, pid_t *ret); +int pidfd_get_uid(int fd, uid_t *ret); +int pidfd_get_cgroupid(int fd, uint64_t *ret); + +int pidfd_get_inode_id(int fd, uint64_t *ret); + +int pidfd_get_inode_id_self_cached(uint64_t *ret); diff --git a/src/libnm-systemd-shared/src/basic/pidref.h b/src/libnm-systemd-shared/src/basic/pidref.h index c440c8b0e0..9e8a39ecfb 100644 --- a/src/libnm-systemd-shared/src/basic/pidref.h +++ b/src/libnm-systemd-shared/src/basic/pidref.h @@ -1,15 +1,50 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +typedef struct PidRef PidRef; + #include "macro.h" +#include "process-util.h" -/* An embeddable structure carrying a reference to a process. Supposed to be used when tracking processes continuously. */ -typedef struct PidRef { - pid_t pid; /* always valid */ - int fd; /* only valid if pidfd are available in the kernel, and we manage to get an fd */ -} PidRef; +/* An embeddable structure carrying a reference to a process. Supposed to be used when tracking processes + * continuously. This combines a PID, a modern Linux pidfd and the 64bit inode number of the pidfd into one + * structure. Note that depending on kernel support the pidfd might not be initialized, and if it is + * initialized then fd_id might still not be initialized (because the concept was added to the kernel much + * later than pidfds themselves). + * + * There are three special states a PidRef can be in: + * + * 1. It can be *unset*. Use pidref_is_set() to detect this case. Most operations attempted on such a PidRef + * will fail with -ESRCH. Use PIDREF_NULL for initializing a PidRef in this state. + * + * 2. It can be marked as *automatic*. This is a special state indicating that a process reference is + * supposed to be derived automatically from the current context. This is used by the Varlink/JSON + * dispatcher as indication that a PidRef shall be derived from the connection peer, but might be + * otherwise used too. When marked *automatic* the PidRef will also be considered *unset*, hence most + * operations will fail with -ESRCH, as above. + * + * 3. It can be marked as *remote*. This is useful when deserializing a PidRef structure from an IPC message + * or similar, and it has been determined that the given PID definitely doesn't refer to a local + * process. In this case the PidRef logic will refrain from trying to acquire a pidfd for the + * process. Moreover, most operations will fail with -EREMOTE. Only PidRef structures that are not marked + * *unset* can be marked *remote*. + */ +struct PidRef { + pid_t pid; /* > 0 if the PidRef is set, otherwise set to PID_AUTOMATIC if automatic mode is + * desired, or 0 otherwise. */ + int fd; /* only valid if pidfd are available in the kernel, and we manage to get an fd. If we + * know that the PID is not from the local machine we set this to -EREMOTE, otherwise + * we use -EBADF as indicator the fd is invalid. */ + uint64_t fd_id; /* the inode number of pidfd. only useful in kernel 6.9+ where pidfds live in + their own pidfs and each process comes with a unique inode number */ +}; -#define PIDREF_NULL (const PidRef) { .fd = -EBADF } +#define PIDREF_NULL (PidRef) { .fd = -EBADF } + +/* A special pidref value that we are using when a PID shall be automatically acquired from some surrounding + * context, for example connection peer. Much like PIDREF_NULL it will be considered unset by + * pidref_is_set().*/ +#define PIDREF_AUTOMATIC (const PidRef) { .pid = PID_AUTOMATIC, .fd = -EBADF } /* Turns a pid_t into a PidRef structure on-the-fly *without* acquiring a pidfd for it. (As opposed to * pidref_set_pid() which does so *with* acquiring one, see below) */ @@ -19,18 +54,17 @@ static inline bool pidref_is_set(const PidRef *pidref) { return pidref && pidref->pid > 0; } -static inline bool pidref_equal(const PidRef *a, const PidRef *b) { +bool pidref_is_automatic(const PidRef *pidref); - if (pidref_is_set(a)) { - if (!pidref_is_set(b)) - return false; - - return a->pid == b->pid; - } - - return !pidref_is_set(b); +static inline bool pidref_is_remote(const PidRef *pidref) { + /* If the fd is set to -EREMOTE we assume PidRef does not refer to a local PID, but on another + * machine (and we just got the PidRef initialized due to deserialization of some RPC message) */ + return pidref_is_set(pidref) && pidref->fd == -EREMOTE; } +int pidref_acquire_pidfd_id(PidRef *pidref); +bool pidref_equal(PidRef *a, PidRef *b); + /* This turns a pid_t into a PidRef structure, and acquires a pidfd for it, if possible. (As opposed to * PIDREF_MAKE_FROM_PID() above, which does not acquire a pidfd.) */ int pidref_set_pid(PidRef *pidref, pid_t pid); @@ -43,13 +77,13 @@ static inline int pidref_set_self(PidRef *pidref) { return pidref_set_pid(pidref, 0); } -bool pidref_is_self(const PidRef *pidref); +bool pidref_is_self(PidRef *pidref); void pidref_done(PidRef *pidref); -PidRef *pidref_free(PidRef *pidref); +PidRef* pidref_free(PidRef *pidref); DEFINE_TRIVIAL_CLEANUP_FUNC(PidRef*, pidref_free); -int pidref_copy(const PidRef *pidref, PidRef *dest); +int pidref_copy(const PidRef *pidref, PidRef *ret); int pidref_dup(const PidRef *pidref, PidRef **ret); int pidref_new_from_pid(pid_t pid, PidRef **ret); @@ -58,8 +92,8 @@ int pidref_kill(const PidRef *pidref, int sig); int pidref_kill_and_sigcont(const PidRef *pidref, int sig); int pidref_sigqueue(const PidRef *pidref, int sig, int value); -int pidref_wait(const PidRef *pidref, siginfo_t *siginfo, int options); -int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret); +int pidref_wait(PidRef *pidref, siginfo_t *siginfo, int options); +int pidref_wait_for_terminate(PidRef *pidref, siginfo_t *ret); static inline void pidref_done_sigkill_wait(PidRef *pidref) { if (!pidref_is_set(pidref)) diff --git a/src/libnm-systemd-shared/src/basic/prioq.c b/src/libnm-systemd-shared/src/basic/prioq.c index 5fbb9998b6..b3cf88ecad 100644 --- a/src/libnm-systemd-shared/src/basic/prioq.c +++ b/src/libnm-systemd-shared/src/basic/prioq.c @@ -24,8 +24,7 @@ struct prioq_item { struct Prioq { compare_func_t compare_func; - unsigned n_items, n_allocated; - + unsigned n_items; struct prioq_item *items; }; @@ -142,28 +141,18 @@ static unsigned shuffle_down(Prioq *q, unsigned idx) { } int prioq_put(Prioq *q, void *data, unsigned *idx) { - struct prioq_item *i; unsigned k; assert(q); - if (q->n_items >= q->n_allocated) { - unsigned n; - struct prioq_item *j; - - n = MAX((q->n_items+1) * 2, 16u); - j = reallocarray(q->items, n, sizeof(struct prioq_item)); - if (!j) - return -ENOMEM; - - q->items = j; - q->n_allocated = n; - } + if (!GREEDY_REALLOC(q->items, MAX(q->n_items + 1, 16u))) + return -ENOMEM; k = q->n_items++; - i = q->items + k; - i->data = data; - i->idx = idx; + q->items[k] = (struct prioq_item) { + .data = data, + .idx = idx, + }; if (idx) *idx = k; diff --git a/src/libnm-systemd-shared/src/basic/process-util.c b/src/libnm-systemd-shared/src/basic/process-util.c index a94ff38409..fbde9c35e6 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.c +++ b/src/libnm-systemd-shared/src/basic/process-util.c @@ -25,6 +25,7 @@ #include "alloc-util.h" #include "architecture.h" #include "argv-util.h" +#include "cgroup-util.h" #include "dirent-util.h" #include "env-file.h" #include "env-util.h" @@ -34,6 +35,7 @@ #include "fileio.h" #include "fs-util.h" #include "hostname-util.h" +#include "io-util.h" #include "locale-util.h" #include "log.h" #include "macro.h" @@ -46,6 +48,7 @@ #include "nulstr-util.h" #include "parse-util.h" #include "path-util.h" +#include "pidfd-util.h" #include "process-util.h" #include "raw-clone.h" #include "rlimit-util.h" @@ -55,6 +58,7 @@ #include "string-table.h" #include "string-util.h" #include "terminal-util.h" +#include "time-util.h" #include "user-util.h" #include "utf8.h" @@ -99,8 +103,8 @@ int pid_get_comm(pid_t pid, char **ret) { _cleanup_free_ char *escaped = NULL, *comm = NULL; int r; - assert(ret); assert(pid >= 0); + assert(ret); if (pid == 0 || pid == getpid_cached()) { comm = new0(char, TASK_COMM_LEN + 1); /* Must fit in 16 byte according to prctl(2) */ @@ -140,6 +144,9 @@ int pidref_get_comm(const PidRef *pid, char **ret) { if (!pidref_is_set(pid)) return -ESRCH; + if (pidref_is_remote(pid)) + return -EREMOTE; + r = pid_get_comm(pid->pid, &comm); if (r < 0) return r; @@ -286,6 +293,9 @@ int pidref_get_cmdline(const PidRef *pid, size_t max_columns, ProcessCmdlineFlag if (!pidref_is_set(pid)) return -ESRCH; + if (pidref_is_remote(pid)) + return -EREMOTE; + r = pid_get_cmdline(pid->pid, max_columns, flags, &s); if (r < 0) return r; @@ -328,6 +338,9 @@ int pidref_get_cmdline_strv(const PidRef *pid, ProcessCmdlineFlags flags, char * if (!pidref_is_set(pid)) return -ESRCH; + if (pidref_is_remote(pid)) + return -EREMOTE; + r = pid_get_cmdline_strv(pid->pid, flags, &args); if (r < 0) return r; @@ -383,33 +396,6 @@ int container_get_leader(const char *machine, pid_t *pid) { return 0; } -int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret) { - int r; - - assert(ret); - - for (;;) { - pid_t ppid; - - r = get_process_ppid(pid, &ppid); - if (r < 0) - return r; - - r = in_same_namespace(pid, ppid, type); - if (r < 0) - return r; - if (r == 0) { - /* If the parent and the child are not in the same - * namespace, then the child is the leader we are - * looking for. */ - *ret = pid; - return 0; - } - - pid = ppid; - } -} - int pid_is_kernel_thread(pid_t pid) { _cleanup_free_ char *line = NULL; unsigned long long flags; @@ -474,6 +460,9 @@ int pidref_is_kernel_thread(const PidRef *pid) { if (!pidref_is_set(pid)) return -ESRCH; + if (pidref_is_remote(pid)) + return -EREMOTE; + result = pid_is_kernel_thread(pid->pid); if (result < 0) return result; @@ -485,22 +474,6 @@ int pidref_is_kernel_thread(const PidRef *pid) { return result; } -int get_process_capeff(pid_t pid, char **ret) { - const char *p; - int r; - - assert(pid >= 0); - assert(ret); - - p = procfs_file_alloca(pid, "status"); - - r = get_proc_field(p, "CapEff", WHITESPACE, ret); - if (r == -ENOENT) - return -ESRCH; - - return r; -} - static int get_process_link_contents(pid_t pid, const char *proc_file, char **ret) { const char *p; int r; @@ -510,7 +483,7 @@ static int get_process_link_contents(pid_t pid, const char *proc_file, char **re p = procfs_file_alloca(pid, proc_file); r = readlink_malloc(p, ret); - return r == -ENOENT ? -ESRCH : r; + return (r == -ENOENT && proc_mounted() > 0) ? -ESRCH : r; } int get_process_exe(pid_t pid, char **ret) { @@ -585,12 +558,21 @@ int pid_get_uid(pid_t pid, uid_t *ret) { } int pidref_get_uid(const PidRef *pid, uid_t *ret) { - uid_t uid; int r; if (!pidref_is_set(pid)) return -ESRCH; + if (pidref_is_remote(pid)) + return -EREMOTE; + + if (pid->fd >= 0) { + r = pidfd_get_uid(pid->fd, ret); + if (!ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return r; + } + + uid_t uid; r = pid_get_uid(pid->pid, &uid); if (r < 0) return r; @@ -676,7 +658,7 @@ int get_process_environ(pid_t pid, char **ret) { return 0; } -int get_process_ppid(pid_t pid, pid_t *ret) { +int pid_get_ppid(pid_t pid, pid_t *ret) { _cleanup_free_ char *line = NULL; unsigned long ppid; const char *p; @@ -684,15 +666,17 @@ int get_process_ppid(pid_t pid, pid_t *ret) { assert(pid >= 0); - if (pid == 0 || pid == getpid_cached()) { + if (pid == 0) + pid = getpid_cached(); + if (pid == 1) /* PID 1 has no parent, shortcut this case */ + return -EADDRNOTAVAIL; + + if (pid == getpid_cached()) { if (ret) *ret = getppid(); return 0; } - if (pid == 1) /* PID 1 has no parent, shortcut this case */ - return -EADDRNOTAVAIL; - p = procfs_file_alloca(pid, "stat"); r = read_one_line_file(p, &line); if (r == -ENOENT) @@ -706,7 +690,6 @@ int get_process_ppid(pid_t pid, pid_t *ret) { p = strrchr(line, ')'); if (!p) return -EIO; - p++; if (sscanf(p, " " @@ -715,9 +698,9 @@ int get_process_ppid(pid_t pid, pid_t *ret) { &ppid) != 1) return -EIO; - /* If ppid is zero the process has no parent. Which might be the case for PID 1 but also for - * processes originating in other namespaces that are inserted into a pidns. Return a recognizable - * error in this case. */ + /* If ppid is zero the process has no parent. Which might be the case for PID 1 (caught above) + * but also for processes originating in other namespaces that are inserted into a pidns. + * Return a recognizable error in this case. */ if (ppid == 0) return -EADDRNOTAVAIL; @@ -730,7 +713,74 @@ int get_process_ppid(pid_t pid, pid_t *ret) { return 0; } -int pid_get_start_time(pid_t pid, uint64_t *ret) { +int pidref_get_ppid(const PidRef *pidref, pid_t *ret) { + int r; + + if (!pidref_is_set(pidref)) + return -ESRCH; + + if (pidref_is_remote(pidref)) + return -EREMOTE; + + if (pidref->fd >= 0) { + r = pidfd_get_ppid(pidref->fd, ret); + if (!ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return r; + } + + pid_t ppid; + r = pid_get_ppid(pidref->pid, ret ? &ppid : NULL); + if (r < 0) + return r; + + r = pidref_verify(pidref); + if (r < 0) + return r; + + if (ret) + *ret = ppid; + return 0; +} + +int pidref_get_ppid_as_pidref(const PidRef *pidref, PidRef *ret) { + pid_t ppid; + int r; + + assert(ret); + + r = pidref_get_ppid(pidref, &ppid); + if (r < 0) + return r; + + for (unsigned attempt = 0; attempt < 16; attempt++) { + _cleanup_(pidref_done) PidRef parent = PIDREF_NULL; + + r = pidref_set_pid(&parent, ppid); + if (r < 0) + return r; + + /* If we have a pidfd of the original PID, let's verify that the process we acquired really + * is the parent still */ + if (pidref->fd >= 0) { + r = pidref_get_ppid(pidref, &ppid); + if (r < 0) + return r; + + /* Did the PPID change since we queried it? if so we might have pinned the wrong + * process, if its PID got reused by now. Let's try again */ + if (parent.pid != ppid) + continue; + } + + *ret = TAKE_PIDREF(parent); + return 0; + } + + /* Give up after 16 tries */ + return -ENOTRECOVERABLE; +} + +int pid_get_start_time(pid_t pid, usec_t *ret) { _cleanup_free_ char *line = NULL; const char *p; int r; @@ -750,13 +800,12 @@ int pid_get_start_time(pid_t pid, uint64_t *ret) { p = strrchr(line, ')'); if (!p) return -EIO; - p++; unsigned long llu; if (sscanf(p, " " - "%*c " /* state */ + "%*c " /* state */ "%*u " /* ppid */ "%*u " /* pgrp */ "%*u " /* session */ @@ -780,18 +829,21 @@ int pid_get_start_time(pid_t pid, uint64_t *ret) { return -EIO; if (ret) - *ret = llu; + *ret = jiffies_to_usec(llu); /* CLOCK_BOOTTIME */ return 0; } -int pidref_get_start_time(const PidRef *pid, uint64_t *ret) { - uint64_t t; +int pidref_get_start_time(const PidRef *pid, usec_t *ret) { + usec_t t; int r; if (!pidref_is_set(pid)) return -ESRCH; + if (pidref_is_remote(pid)) + return -EREMOTE; + r = pid_get_start_time(pid->pid, ret ? &t : NULL); if (r < 0) return r; @@ -1022,7 +1074,6 @@ int kill_and_sigcont(pid_t pid, int sig) { int getenv_for_pid(pid_t pid, const char *field, char **ret) { _cleanup_fclose_ FILE *f = NULL; - char *value = NULL; const char *path; size_t sum = 0; int r; @@ -1031,22 +1082,8 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { assert(field); assert(ret); - if (pid == 0 || pid == getpid_cached()) { - const char *e; - - e = getenv(field); - if (!e) { - *ret = NULL; - return 0; - } - - value = strdup(e); - if (!value) - return -ENOMEM; - - *ret = value; - return 1; - } + if (pid == 0 || pid == getpid_cached()) + return strdup_to_full(ret, getenv(field)); if (!pid_is_valid(pid)) return -EINVAL; @@ -1075,78 +1112,55 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { sum += r; match = startswith(line, field); - if (match && *match == '=') { - value = strdup(match + 1); - if (!value) - return -ENOMEM; - - *ret = value; - return 1; - } + if (match && *match == '=') + return strdup_to_full(ret, match + 1); } *ret = NULL; return 0; } -int pid_is_my_child(pid_t pid) { - pid_t ppid; +int pidref_is_my_child(PidRef *pid) { int r; - if (pid < 0) + if (!pidref_is_set(pid)) return -ESRCH; - if (pid <= 1) + if (pidref_is_remote(pid)) + return -EREMOTE; + + if (pid->pid == 1 || pidref_is_self(pid)) return false; - r = get_process_ppid(pid, &ppid); + pid_t ppid; + r = pidref_get_ppid(pid, &ppid); + if (r == -EADDRNOTAVAIL) /* if this process is outside of our pidns, it is definitely not our child */ + return false; if (r < 0) return r; return ppid == getpid_cached(); } -int pidref_is_my_child(const PidRef *pid) { - int r, result; +int pid_is_my_child(pid_t pid) { - if (!pidref_is_set(pid)) - return -ESRCH; + if (pid == 0) + return false; - result = pid_is_my_child(pid->pid); - if (result < 0) - return result; - - r = pidref_verify(pid); - if (r < 0) - return r; - - return result; + return pidref_is_my_child(&PIDREF_MAKE_FROM_PID(pid)); } -int pid_is_unwaited(pid_t pid) { - /* Checks whether a PID is still valid at all, including a zombie */ - - if (pid < 0) - return -ESRCH; - - if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */ - return true; - - if (pid == getpid_cached()) - return true; - - if (kill(pid, 0) >= 0) - return true; - - return errno != ESRCH; -} - -int pidref_is_unwaited(const PidRef *pid) { +int pidref_is_unwaited(PidRef *pid) { int r; + /* Checks whether a PID is still valid at all, including a zombie */ + if (!pidref_is_set(pid)) return -ESRCH; + if (pidref_is_remote(pid)) + return -EREMOTE; + if (pid->pid == 1 || pidref_is_self(pid)) return true; @@ -1159,6 +1173,14 @@ int pidref_is_unwaited(const PidRef *pid) { return true; } +int pid_is_unwaited(pid_t pid) { + + if (pid == 0) + return true; + + return pidref_is_unwaited(&PIDREF_MAKE_FROM_PID(pid)); +} + int pid_is_alive(pid_t pid) { int r; @@ -1188,6 +1210,9 @@ int pidref_is_alive(const PidRef *pidref) { if (!pidref_is_set(pidref)) return -ESRCH; + if (pidref_is_remote(pidref)) + return -EREMOTE; + result = pid_is_alive(pidref->pid); if (result < 0) { assert(result != -ESRCH); @@ -1203,27 +1228,62 @@ int pidref_is_alive(const PidRef *pidref) { return result; } -int pid_from_same_root_fs(pid_t pid) { - const char *root; +int pidref_from_same_root_fs(PidRef *a, PidRef *b) { + _cleanup_(pidref_done) PidRef self = PIDREF_NULL; + int r; - if (pid < 0) + /* Checks if the two specified processes have the same root fs. Either can be specified as NULL in + * which case we'll check against ourselves. */ + + if (!a || !b) { + r = pidref_set_self(&self); + if (r < 0) + return r; + if (!a) + a = &self; + if (!b) + b = &self; + } + + if (!pidref_is_set(a) || !pidref_is_set(b)) + return -ESRCH; + + /* If one of the two processes have the same root they cannot have the same root fs, but if both of + * them do we don't know */ + if (pidref_is_remote(a) && pidref_is_remote(b)) + return -EREMOTE; + if (pidref_is_remote(a) || pidref_is_remote(b)) return false; - if (pid == 0 || pid == getpid_cached()) + if (pidref_equal(a, b)) return true; - root = procfs_file_alloca(pid, "root"); + const char *roota = procfs_file_alloca(a->pid, "root"); + const char *rootb = procfs_file_alloca(b->pid, "root"); - return inode_same(root, "/proc/1/root", 0); + int result = inode_same(roota, rootb, 0); + if (result == -ENOENT) + return proc_mounted() == 0 ? -ENOSYS : -ESRCH; + if (result < 0) + return result; + + r = pidref_verify(a); + if (r < 0) + return r; + r = pidref_verify(b); + if (r < 0) + return r; + + return result; } bool is_main_thread(void) { - static thread_local int cached = 0; + static thread_local int cached = -1; - if (_unlikely_(cached == 0)) - cached = getpid_cached() == gettid() ? 1 : -1; + if (cached < 0) + cached = getpid_cached() == gettid(); - return cached > 0; + return cached; } bool oom_score_adjust_is_valid(int oa) { @@ -1303,7 +1363,7 @@ int opinionated_personality(unsigned long *ret) { if (current < 0) return current; - if (((unsigned long) current & 0xffff) == PER_LINUX32) + if (((unsigned long) current & OPINIONATED_PERSONALITY_MASK) == PER_LINUX32) *ret = PER_LINUX32; else *ret = PER_LINUX; @@ -1415,11 +1475,6 @@ int must_be_root(void) { return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be root."); } -static void restore_sigsetp(sigset_t **ssp) { - if (*ssp) - (void) sigprocmask(SIG_SETMASK, *ssp, NULL); -} - pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata) { size_t ps; pid_t pid; @@ -1459,6 +1514,11 @@ pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata) { return pid; } +static void restore_sigsetp(sigset_t **ssp) { + if (*ssp) + (void) sigprocmask(SIG_SETMASK, *ssp, NULL); +} + static int fork_flags_to_signal(ForkFlags flags) { return (flags & FORK_DEATHSIG_SIGTERM) ? SIGTERM : (flags & FORK_DEATHSIG_SIGINT) ? SIGINT : @@ -1468,7 +1528,7 @@ static int fork_flags_to_signal(ForkFlags flags) { int safe_fork_full( const char *name, const int stdio_fds[3], - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid) { @@ -1479,8 +1539,8 @@ int safe_fork_full( bool block_signals = false, block_all = false, intermediary = false; int prio, r; - assert(!FLAGS_SET(flags, FORK_DETACH) || !ret_pid); - assert(!FLAGS_SET(flags, FORK_DETACH|FORK_WAIT)); + assert(!FLAGS_SET(flags, FORK_DETACH) || + (!ret_pid && (flags & (FORK_WAIT|FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGINT|FORK_DEATHSIG_SIGKILL)) == 0)); /* A wrapper around fork(), that does a couple of important initializations in addition to mere forking. Always * returns the child's PID in *ret_pid. Returns == 0 in the child, and > 0 in the parent. */ @@ -1511,15 +1571,12 @@ int safe_fork_full( } if (block_signals) { - if (sigprocmask(SIG_SETMASK, &ss, &saved_ss) < 0) - return log_full_errno(prio, errno, "Failed to set signal mask: %m"); + if (sigprocmask(SIG_BLOCK, &ss, &saved_ss) < 0) + return log_full_errno(prio, errno, "Failed to block signal mask: %m"); saved_ssp = &saved_ss; } if (FLAGS_SET(flags, FORK_DETACH)) { - assert(!FLAGS_SET(flags, FORK_WAIT)); - assert(!ret_pid); - /* Fork off intermediary child if needed */ r = is_reaper_process(); @@ -1541,11 +1598,12 @@ int safe_fork_full( } } - if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS|FORK_NEW_NETNS)) != 0) + if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS|FORK_NEW_NETNS|FORK_NEW_PIDNS)) != 0) pid = raw_clone(SIGCHLD| (FLAGS_SET(flags, FORK_NEW_MOUNTNS) ? CLONE_NEWNS : 0) | (FLAGS_SET(flags, FORK_NEW_USERNS) ? CLONE_NEWUSER : 0) | - (FLAGS_SET(flags, FORK_NEW_NETNS) ? CLONE_NEWNET : 0)); + (FLAGS_SET(flags, FORK_NEW_NETNS) ? CLONE_NEWNET : 0) | + (FLAGS_SET(flags, FORK_NEW_PIDNS) ? CLONE_NEWPID : 0)); else pid = fork(); if (pid < 0) @@ -1697,6 +1755,19 @@ int safe_fork_full( } } + if (flags & FORK_PACK_FDS) { + /* FORK_CLOSE_ALL_FDS ensures that except_fds are the only FDs >= 3 that are + * open, this is including the log. This is required by pack_fds, which will + * get stuck in an infinite loop of any FDs other than except_fds are open. */ + assert(FLAGS_SET(flags, FORK_CLOSE_ALL_FDS)); + + r = pack_fds(except_fds, n_except_fds); + if (r < 0) { + log_full_errno(prio, r, "Failed to pack file descriptors: %m"); + _exit(EXIT_FAILURE); + } + } + if (flags & FORK_CLOEXEC_OFF) { r = fd_cloexec_many(except_fds, n_except_fds, false); if (r < 0) { @@ -1727,6 +1798,9 @@ int safe_fork_full( } } + if (FLAGS_SET(flags, FORK_FREEZE)) + freeze(); + if (ret_pid) *ret_pid = getpid_cached(); @@ -1736,7 +1810,7 @@ int safe_fork_full( int pidref_safe_fork_full( const char *name, const int stdio_fds[3], - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, PidRef *ret_pid) { @@ -1744,12 +1818,16 @@ int pidref_safe_fork_full( pid_t pid; int r, q; - assert(!FLAGS_SET(flags, FORK_WAIT)); - r = safe_fork_full(name, stdio_fds, except_fds, n_except_fds, flags, &pid); - if (r < 0) + if (r < 0 || !ret_pid) return r; + if (r > 0 && FLAGS_SET(flags, FORK_WAIT)) { + /* If we are in the parent and successfully waited, then the process doesn't exist anymore */ + *ret_pid = PIDREF_NULL; + return r; + } + q = pidref_set_pid(ret_pid, pid); if (q < 0) /* Let's not fail for this, no matter what, the process exists after all, and that's key */ *ret_pid = PIDREF_MAKE_FROM_PID(pid); @@ -1760,7 +1838,7 @@ int pidref_safe_fork_full( int namespace_fork( const char *outer_name, const char *inner_name, - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, @@ -1820,6 +1898,9 @@ int namespace_fork( int set_oom_score_adjust(int value) { char t[DECIMAL_STR_MAX(int)]; + if (!oom_score_adjust_is_valid(value)) + return -EINVAL; + xsprintf(t, "%i", value); return write_string_file("/proc/self/oom_score_adj", t, @@ -1836,69 +1917,19 @@ int get_oom_score_adjust(int *ret) { delete_trailing_chars(t, WHITESPACE); - assert_se(safe_atoi(t, &a) >= 0); - assert_se(oom_score_adjust_is_valid(a)); + r = safe_atoi(t, &a); + if (r < 0) + return r; + + if (!oom_score_adjust_is_valid(a)) + return -ENODATA; if (ret) *ret = a; + return 0; } -int pidfd_get_pid(int fd, pid_t *ret) { - char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; - _cleanup_free_ char *fdinfo = NULL; - char *p; - int r; - - /* Converts a pidfd into a pid. Well known errors: - * - * -EBADF → fd invalid - * -ENOSYS → /proc/ not mounted - * -ENOTTY → fd valid, but not a pidfd - * -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one - * -ESRCH → fd valid, but process is already reaped - */ - - if (fd < 0) - return -EBADF; - - xsprintf(path, "/proc/self/fdinfo/%i", fd); - - r = read_full_virtual_file(path, &fdinfo, NULL); - if (r == -ENOENT) /* if fdinfo doesn't exist we assume the process does not exist */ - return proc_mounted() > 0 ? -EBADF : -ENOSYS; - if (r < 0) - return r; - - p = find_line_startswith(fdinfo, "Pid:"); - if (!p) - return -ENOTTY; /* not a pidfd? */ - - p += strspn(p, WHITESPACE); - p[strcspn(p, WHITESPACE)] = 0; - - if (streq(p, "0")) - return -EREMOTE; /* PID is in foreign PID namespace? */ - if (streq(p, "-1")) - return -ESRCH; /* refers to reaped process? */ - - return parse_pid(p, ret); -} - -int pidfd_verify_pid(int pidfd, pid_t pid) { - pid_t current_pid; - int r; - - assert(pidfd >= 0); - assert(pid > 0); - - r = pidfd_get_pid(pidfd, ¤t_pid); - if (r < 0) - return r; - - return current_pid != pid ? -ESRCH : 0; -} - static int rlimit_to_nice(rlim_t limit) { if (limit <= 1) return PRIO_MAX-1; /* i.e. 19 */ @@ -1910,17 +1941,15 @@ static int rlimit_to_nice(rlim_t limit) { } int setpriority_closest(int priority) { - int current, limit, saved_errno; struct rlimit highest; + int r, current, limit; /* Try to set requested nice level */ - if (setpriority(PRIO_PROCESS, 0, priority) >= 0) + r = RET_NERRNO(setpriority(PRIO_PROCESS, 0, priority)); + if (r >= 0) return 1; - - /* Permission failed */ - saved_errno = -errno; - if (!ERRNO_IS_PRIVILEGE(saved_errno)) - return saved_errno; + if (!ERRNO_IS_NEG_PRIVILEGE(r)) + return r; errno = 0; current = getpriority(PRIO_PROCESS, 0); @@ -1934,24 +1963,21 @@ int setpriority_closest(int priority) { * then the whole setpriority() system call is blocked to us, hence let's propagate the error * right-away */ if (priority > current) - return saved_errno; + return r; if (getrlimit(RLIMIT_NICE, &highest) < 0) return -errno; limit = rlimit_to_nice(highest.rlim_cur); - /* We are already less nice than limit allows us */ - if (current < limit) { - log_debug("Cannot raise nice level, permissions and the resource limit do not allow it."); - return 0; - } + /* Push to the allowed limit if we're higher than that. Note that we could also be less nice than + * limit allows us, but still higher than what's requested. In that case our current value is + * the best choice. */ + if (current > limit) + if (setpriority(PRIO_PROCESS, 0, limit) < 0) + return -errno; - /* Push to the allowed limit */ - if (setpriority(PRIO_PROCESS, 0, limit) < 0) - return -errno; - - log_debug("Cannot set requested nice level (%i), used next best (%i).", priority, limit); + log_debug("Cannot set requested nice level (%i), using next best (%i).", priority, MIN(current, limit)); return 0; } @@ -1971,7 +1997,8 @@ _noreturn_ void freeze(void) { break; } - /* waitid() failed with an unexpected error, things are really borked. Freeze now! */ + /* waitid() failed with an ECHLD error (because there are no left-over child processes) or any other + * (unexpected) error. Freeze for good now! */ for (;;) pause(); } @@ -2043,7 +2070,7 @@ int posix_spawn_wrapper( const char *cgroup, PidRef *ret_pidref) { - short flags = POSIX_SPAWN_SETSIGMASK|POSIX_SPAWN_SETSIGDEF; + short flags = POSIX_SPAWN_SETSIGMASK; posix_spawnattr_t attr; sigset_t mask; int r; @@ -2054,7 +2081,7 @@ int posix_spawn_wrapper( * issues. * * Also, move the newly-created process into 'cgroup' through POSIX_SPAWN_SETCGROUP (clone3()) - * if available. Note that CLONE_INTO_CGROUP is only supported on cgroup v2. + * if available. * returns 1: We're already in the right cgroup * 0: 'cgroup' not specified or POSIX_SPAWN_SETCGROUP is not supported. The caller * needs to call 'cg_attach' on their own */ @@ -2073,9 +2100,10 @@ int posix_spawn_wrapper( _unused_ _cleanup_(posix_spawnattr_destroyp) posix_spawnattr_t *attr_destructor = &attr; #if HAVE_PIDFD_SPAWN + static bool have_clone_into_cgroup = true; /* kernel 5.7+ */ _cleanup_close_ int cgroup_fd = -EBADF; - if (cgroup) { + if (cgroup && have_clone_into_cgroup) { _cleanup_free_ char *resolved_cgroup = NULL; r = cg_get_path_and_check( @@ -2109,25 +2137,38 @@ int posix_spawn_wrapper( _cleanup_close_ int pidfd = -EBADF; r = pidfd_spawn(&pidfd, path, NULL, &attr, argv, envp); - if (r == 0) { - r = pidref_set_pidfd_consume(ret_pidref, TAKE_FD(pidfd)); - if (r < 0) - return r; + if (ERRNO_IS_NOT_SUPPORTED(r) && FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP) && cg_is_threaded(cgroup) > 0) + return -EUCLEAN; /* clone3() could also return EOPNOTSUPP if the target cgroup is in threaded mode, + turn that into something recognizable */ + if ((ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_PRIVILEGE(r) || r == E2BIG) && + FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP)) { + /* Compiled on a newer host, or seccomp&friends blocking clone3()? Fallback, but + * need to disable POSIX_SPAWN_SETCGROUP, which is what redirects to clone3(). + * Note that we might get E2BIG here since some kernels (e.g. 5.4) support clone3() + * but not CLONE_INTO_CGROUP. */ - return FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP); + /* CLONE_INTO_CGROUP definitely won't work, hence remember the fact so that we don't + * retry every time. */ + have_clone_into_cgroup = false; + + flags &= ~POSIX_SPAWN_SETCGROUP; + r = posix_spawnattr_setflags(&attr, flags); + if (r != 0) + return -r; + + r = pidfd_spawn(&pidfd, path, NULL, &attr, argv, envp); } - if (!(ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_PRIVILEGE(r))) - return -r; - - /* Compiled on a newer host, or seccomp&friends blocking clone3()? Fallback, but need to change the - * flags to remove the cgroup one, which is what redirects to clone3() */ - flags &= ~POSIX_SPAWN_SETCGROUP; - r = posix_spawnattr_setflags(&attr, flags); if (r != 0) return -r; -#endif + r = pidref_set_pidfd_consume(ret_pidref, TAKE_FD(pidfd)); + if (r < 0) + return r; + + return FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP); +#else pid_t pid; + r = posix_spawn(&pid, path, NULL, &attr, argv, envp); if (r != 0) return -r; @@ -2137,6 +2178,7 @@ int posix_spawn_wrapper( return r; return 0; /* We did not use CLONE_INTO_CGROUP so return 0, the caller will have to move the child */ +#endif } int proc_dir_open(DIR **ret) { @@ -2227,3 +2269,45 @@ static const char* const sched_policy_table[] = { }; DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX); + +_noreturn_ void report_errno_and_exit(int errno_fd, int error) { + int r; + + if (error >= 0) + _exit(EXIT_SUCCESS); + + assert(errno_fd >= 0); + + r = loop_write(errno_fd, &error, sizeof(error)); + if (r < 0) + log_debug_errno(r, "Failed to write errno to errno_fd=%d: %m", errno_fd); + + _exit(EXIT_FAILURE); +} + +int read_errno(int errno_fd) { + int r; + + assert(errno_fd >= 0); + + /* The issue here is that it's impossible to distinguish between an error code returned by child and + * IO error arose when reading it. So, the function logs errors and return EIO for the later case. */ + + ssize_t n = loop_read(errno_fd, &r, sizeof(r), /* do_poll = */ false); + if (n < 0) { + log_debug_errno(n, "Failed to read errno: %m"); + return -EIO; + } + if (n == sizeof(r)) { + if (r == 0) + return 0; + if (r < 0) /* child process reported an error, return it */ + return log_debug_errno(r, "Child process failed with errno: %m"); + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Received an errno, but it's a positive value."); + } + if (n != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Received unexpected amount of bytes while reading errno."); + + /* the process exited without reporting an error, assuming success */ + return 0; +} diff --git a/src/libnm-systemd-shared/src/basic/process-util.h b/src/libnm-systemd-shared/src/basic/process-util.h index 0fc31f7086..58fff2b174 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.h +++ b/src/libnm-systemd-shared/src/basic/process-util.h @@ -14,7 +14,7 @@ #include "alloc-util.h" #include "format-util.h" #include "macro.h" -#include "namespace-util.h" +#include "pidref.h" #include "time-util.h" #define procfs_file_alloca(pid, field) \ @@ -49,19 +49,18 @@ int get_process_exe(pid_t pid, char **ret); int pid_get_uid(pid_t pid, uid_t *ret); int pidref_get_uid(const PidRef *pid, uid_t *ret); int get_process_gid(pid_t pid, gid_t *ret); -int get_process_capeff(pid_t pid, char **ret); int get_process_cwd(pid_t pid, char **ret); int get_process_root(pid_t pid, char **ret); int get_process_environ(pid_t pid, char **ret); -int get_process_ppid(pid_t pid, pid_t *ret); -int pid_get_start_time(pid_t pid, uint64_t *ret); -int pidref_get_start_time(const PidRef* pid, uint64_t *ret); +int pid_get_ppid(pid_t pid, pid_t *ret); +int pidref_get_ppid(const PidRef *pidref, pid_t *ret); +int pidref_get_ppid_as_pidref(const PidRef *pidref, PidRef *ret); +int pid_get_start_time(pid_t pid, usec_t *ret); +int pidref_get_start_time(const PidRef *pid, usec_t *ret); int get_process_umask(pid_t pid, mode_t *ret); int container_get_leader(const char *machine, pid_t *pid); -int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret); - int wait_for_terminate(pid_t pid, siginfo_t *status); typedef enum WaitFlags { @@ -91,29 +90,34 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value); int pid_is_alive(pid_t pid); int pidref_is_alive(const PidRef *pidref); int pid_is_unwaited(pid_t pid); -int pidref_is_unwaited(const PidRef *pidref); +int pidref_is_unwaited(PidRef *pidref); int pid_is_my_child(pid_t pid); -int pidref_is_my_child(const PidRef *pidref); -int pid_from_same_root_fs(pid_t pid); +int pidref_is_my_child(PidRef *pidref); +int pidref_from_same_root_fs(PidRef *a, PidRef *b); bool is_main_thread(void); bool oom_score_adjust_is_valid(int oa); #ifndef PERSONALITY_INVALID -/* personality(7) documents that 0xffffffffUL is used for querying the +/* personality(2) documents that 0xFFFFFFFFUL is used for querying the * current personality, hence let's use that here as error * indicator. */ -#define PERSONALITY_INVALID 0xffffffffLU +#define PERSONALITY_INVALID 0xFFFFFFFFUL #endif +/* The personality() syscall returns a 32-bit value where the top three bytes are reserved for flags that + * emulate historical or architectural quirks, and only the least significant byte reflects the actual + * personality we're interested in. */ +#define OPINIONATED_PERSONALITY_MASK 0xFFUL + unsigned long personality_from_string(const char *p); -const char *personality_to_string(unsigned long); +const char* personality_to_string(unsigned long); int safe_personality(unsigned long p); int opinionated_personality(unsigned long *ret); -const char *sigchld_code_to_string(int i) _const_; +const char* sigchld_code_to_string(int i) _const_; int sigchld_code_from_string(const char *s) _pure_; int sched_policy_to_string_alloc(int i, char **s); @@ -143,10 +147,16 @@ static inline bool sched_priority_is_valid(int i) { return i >= 0 && i <= sched_get_priority_max(SCHED_RR); } +#define PID_AUTOMATIC ((pid_t) INT_MIN) /* special value indicating "acquire pid from connection peer" */ + static inline bool pid_is_valid(pid_t p) { return p > 0; } +static inline bool pid_is_automatic(pid_t p) { + return p == PID_AUTOMATIC; +} + pid_t getpid_cached(void); void reset_cached_pid(void); @@ -154,7 +164,7 @@ int must_be_root(void); pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata); -/* 💣 Note that FORK_NEW_USERNS, FORK_NEW_MOUNTNS, or FORK_NEW_NETNS should not be called in threaded +/* 💣 Note that FORK_NEW_USERNS, FORK_NEW_MOUNTNS, FORK_NEW_NETNS or FORK_NEW_PIDNS should not be called in threaded * programs, because they cause us to use raw_clone() which does not synchronize the glibc malloc() locks, * and thus will cause deadlocks if the parent uses threads and the child does memory allocations. Hence: if * the parent is threaded these flags may not be used. These flags cannot be used if the parent uses threads @@ -169,23 +179,26 @@ typedef enum ForkFlags { FORK_REOPEN_LOG = 1 << 6, /* Reopen log connection */ FORK_LOG = 1 << 7, /* Log above LOG_DEBUG log level about failures */ FORK_WAIT = 1 << 8, /* Wait until child exited */ - FORK_NEW_MOUNTNS = 1 << 9, /* Run child in its own mount namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ - FORK_MOUNTNS_SLAVE = 1 << 10, /* Make child's mount namespace MS_SLAVE */ - FORK_PRIVATE_TMP = 1 << 11, /* Mount new /tmp/ in the child (combine with FORK_NEW_MOUNTNS!) */ - FORK_RLIMIT_NOFILE_SAFE = 1 << 12, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */ - FORK_STDOUT_TO_STDERR = 1 << 13, /* Make stdout a copy of stderr */ - FORK_FLUSH_STDIO = 1 << 14, /* fflush() stdout (and stderr) before forking */ - FORK_NEW_USERNS = 1 << 15, /* Run child in its own user namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ - FORK_CLOEXEC_OFF = 1 << 16, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */ - FORK_KEEP_NOTIFY_SOCKET = 1 << 17, /* Unless this specified, $NOTIFY_SOCKET will be unset. */ - FORK_DETACH = 1 << 18, /* Double fork if needed to ensure PID1/subreaper is parent */ - FORK_NEW_NETNS = 1 << 19, /* Run child in its own network namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ + FORK_MOUNTNS_SLAVE = 1 << 9, /* Make child's mount namespace MS_SLAVE */ + FORK_PRIVATE_TMP = 1 << 10, /* Mount new /tmp/ in the child (combine with FORK_NEW_MOUNTNS!) */ + FORK_RLIMIT_NOFILE_SAFE = 1 << 11, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */ + FORK_STDOUT_TO_STDERR = 1 << 12, /* Make stdout a copy of stderr */ + FORK_FLUSH_STDIO = 1 << 13, /* fflush() stdout (and stderr) before forking */ + FORK_CLOEXEC_OFF = 1 << 14, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */ + FORK_KEEP_NOTIFY_SOCKET = 1 << 15, /* Unless this specified, $NOTIFY_SOCKET will be unset. */ + FORK_DETACH = 1 << 16, /* Double fork if needed to ensure PID1/subreaper is parent */ + FORK_PACK_FDS = 1 << 17, /* Rearrange the passed FDs to be FD 3,4,5,etc. Updates the array in place (combine with FORK_CLOSE_ALL_FDS!) */ + FORK_NEW_MOUNTNS = 1 << 18, /* Run child in its own mount namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ + FORK_NEW_USERNS = 1 << 19, /* Run child in its own user namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ + FORK_NEW_NETNS = 1 << 20, /* Run child in its own network namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ + FORK_NEW_PIDNS = 1 << 21, /* Run child in its own PID namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ + FORK_FREEZE = 1 << 22, /* Don't return in child, just call freeze() instead */ } ForkFlags; int safe_fork_full( const char *name, const int stdio_fds[3], - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid); @@ -197,7 +210,7 @@ static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) { int pidref_safe_fork_full( const char *name, const int stdio_fds[3], - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, PidRef *ret_pid); @@ -206,7 +219,18 @@ static inline int pidref_safe_fork(const char *name, ForkFlags flags, PidRef *re return pidref_safe_fork_full(name, NULL, NULL, 0, flags, ret_pid); } -int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid); +int namespace_fork( + const char *outer_name, + const char *inner_name, + int except_fds[], + size_t n_except_fds, + ForkFlags flags, + int pidns_fd, + int mntns_fd, + int netns_fd, + int userns_fd, + int root_fd, + pid_t *ret_pid); int set_oom_score_adjust(int value); int get_oom_score_adjust(int *ret); @@ -226,9 +250,6 @@ assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX); /* Like TAKE_PTR() but for pid_t, resetting them to 0 */ #define TAKE_PID(pid) TAKE_GENERIC(pid, pid_t, 0) -int pidfd_get_pid(int fd, pid_t *ret); -int pidfd_verify_pid(int pidfd, pid_t pid); - int setpriority_closest(int priority); _noreturn_ void freeze(void); @@ -248,3 +269,6 @@ int posix_spawn_wrapper( int proc_dir_open(DIR **ret); int proc_dir_read(DIR *d, pid_t *ret); int proc_dir_read_pidref(DIR *d, PidRef *ret); + +_noreturn_ void report_errno_and_exit(int errno_fd, int error); +int read_errno(int errno_fd); diff --git a/src/libnm-systemd-shared/src/basic/random-util.c b/src/libnm-systemd-shared/src/basic/random-util.c index c7277ad01e..fec4f81035 100644 --- a/src/libnm-systemd-shared/src/basic/random-util.c +++ b/src/libnm-systemd-shared/src/basic/random-util.c @@ -8,29 +8,28 @@ #include #include #include +#include #include #include -#if HAVE_SYS_AUXV_H -# include -#endif - #include "alloc-util.h" #include "env-util.h" #include "errno-util.h" #include "fd-util.h" #include "fileio.h" #include "io-util.h" +#include "iovec-util.h" #include "missing_random.h" #include "missing_syscall.h" #include "missing_threads.h" #include "parse-util.h" +#include "pidfd-util.h" #include "process-util.h" #include "random-util.h" #include "sha256.h" #include "time-util.h" -/* This is a "best effort" kind of thing, but has no real security value. So, this should only be used by +/* This is a "best effort" kind of thing, but has no real security value. So, this should only be used by * random_bytes(), which is not meant for crypto. This could be made better, but we're *not* trying to roll a * userspace prng here, or even have forward secrecy, but rather just do the shortest thing that is at least * better than libc rand(). */ @@ -41,10 +40,10 @@ static void fallback_random_bytes(void *p, size_t n) { uint64_t call_id, block_id; usec_t stamp_mono, stamp_real; pid_t pid, tid; + uint64_t pidfdid; uint8_t auxval[16]; } state = { /* Arbitrary domain separation to prevent other usage of AT_RANDOM from clashing. */ - .label = "systemd fallback random bytes v1", .call_id = fallback_counter++, .stamp_mono = now(CLOCK_MONOTONIC), .stamp_real = now(CLOCK_REALTIME), @@ -52,9 +51,9 @@ static void fallback_random_bytes(void *p, size_t n) { .tid = gettid(), }; -#if HAVE_SYS_AUXV_H + memcpy(state.label, "systemd fallback random bytes v1", sizeof(state.label)); memcpy(state.auxval, ULONG_TO_PTR(getauxval(AT_RANDOM)), sizeof(state.auxval)); -#endif + (void) pidfd_get_inode_id_self_cached(&state.pidfdid); while (n > 0) { struct sha256_ctx ctx; @@ -75,8 +74,9 @@ static void fallback_random_bytes(void *p, size_t n) { } void random_bytes(void *p, size_t n) { - static bool have_getrandom = true, have_grndinsecure = true; - _cleanup_close_ int fd = -EBADF; + static bool have_grndinsecure = true; + + assert(p || n == 0); if (n == 0) return; @@ -84,32 +84,26 @@ void random_bytes(void *p, size_t n) { for (;;) { ssize_t l; - if (!have_getrandom) - break; - l = getrandom(p, n, have_grndinsecure ? GRND_INSECURE : GRND_NONBLOCK); - if (l > 0) { - if ((size_t) l == n) - return; /* Done reading, success. */ - p = (uint8_t *) p + l; - n -= l; - continue; /* Interrupted by a signal; keep going. */ - } else if (l == 0) - break; /* Weird, so fallback to /dev/urandom. */ - else if (ERRNO_IS_NOT_SUPPORTED(errno)) { - have_getrandom = false; - break; /* No syscall, so fallback to /dev/urandom. */ - } else if (errno == EINVAL && have_grndinsecure) { + if (l < 0 && errno == EINVAL && have_grndinsecure) { + /* No GRND_INSECURE; fallback to GRND_NONBLOCK. */ have_grndinsecure = false; - continue; /* No GRND_INSECURE; fallback to GRND_NONBLOCK. */ - } else if (errno == EAGAIN && !have_grndinsecure) - break; /* Will block, but no GRND_INSECURE, so fallback to /dev/urandom. */ + continue; + } + if (l <= 0) + break; /* Will block (with GRND_NONBLOCK), or unexpected error. Give up and fallback + to /dev/urandom. */ - break; /* Unexpected, so just give up and fallback to /dev/urandom. */ + if ((size_t) l == n) + return; /* Done reading, success. */ + + p = (uint8_t *) p + l; + n -= l; + /* Interrupted by a signal; keep going. */ } - fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd >= 0 && loop_read_exact(fd, p, n, false) == 0) + _cleanup_close_ int fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd >= 0 && loop_read_exact(fd, p, n, false) >= 0) return; /* This is a terrible fallback. Oh well. */ @@ -117,8 +111,7 @@ void random_bytes(void *p, size_t n) { } int crypto_random_bytes(void *p, size_t n) { - static bool have_getrandom = true, seen_initialized = false; - _cleanup_close_ int fd = -EBADF; + assert(p || n == 0); if (n == 0) return 0; @@ -126,42 +119,37 @@ int crypto_random_bytes(void *p, size_t n) { for (;;) { ssize_t l; - if (!have_getrandom) - break; - l = getrandom(p, n, 0); - if (l > 0) { - if ((size_t) l == n) - return 0; /* Done reading, success. */ - p = (uint8_t *) p + l; - n -= l; - continue; /* Interrupted by a signal; keep going. */ - } else if (l == 0) - return -EIO; /* Weird, should never happen. */ - else if (ERRNO_IS_NOT_SUPPORTED(errno)) { - have_getrandom = false; - break; /* No syscall, so fallback to /dev/urandom. */ - } - return -errno; - } - - if (!seen_initialized) { - _cleanup_close_ int ready_fd = -EBADF; - int r; - - ready_fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (ready_fd < 0) + if (l < 0) return -errno; - r = fd_wait_for_event(ready_fd, POLLIN, USEC_INFINITY); - if (r < 0) - return r; - seen_initialized = true; - } + if (l == 0) + return -EIO; /* Weird, should never happen. */ - fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return -errno; - return loop_read_exact(fd, p, n, false); + if ((size_t) l == n) + return 0; /* Done reading, success. */ + + p = (uint8_t *) p + l; + n -= l; + /* Interrupted by a signal; keep going. */ + } +} + +int crypto_random_bytes_allocate_iovec(size_t n, struct iovec *ret) { + _cleanup_free_ void *p = NULL; + int r; + + assert(ret); + + p = malloc(MAX(n, 1U)); + if (!p) + return -ENOMEM; + + r = crypto_random_bytes(p, n); + if (r < 0) + return r; + + *ret = IOVEC_MAKE(TAKE_PTR(p), n); + return 0; } size_t random_pool_size(void) { diff --git a/src/libnm-systemd-shared/src/basic/random-util.h b/src/libnm-systemd-shared/src/basic/random-util.h index b1a4d10971..0b5ba77190 100644 --- a/src/libnm-systemd-shared/src/basic/random-util.h +++ b/src/libnm-systemd-shared/src/basic/random-util.h @@ -4,9 +4,11 @@ #include #include #include +#include void random_bytes(void *p, size_t n); /* Returns random bytes suitable for most uses, but may be insecure sometimes. */ int crypto_random_bytes(void *p, size_t n); /* Returns secure random bytes after waiting for the RNG to initialize. */ +int crypto_random_bytes_allocate_iovec(size_t n, struct iovec *ret); static inline uint64_t random_u64(void) { uint64_t u; diff --git a/src/libnm-systemd-shared/src/basic/ratelimit.c b/src/libnm-systemd-shared/src/basic/ratelimit.c index 41ca0709e8..df8c95c562 100644 --- a/src/libnm-systemd-shared/src/basic/ratelimit.c +++ b/src/libnm-systemd-shared/src/basic/ratelimit.c @@ -8,37 +8,37 @@ /* Modelled after Linux' lib/ratelimit.c by Dave Young * , which is licensed GPLv2. */ -bool ratelimit_below(RateLimit *r) { +bool ratelimit_below(RateLimit *rl) { usec_t ts; - assert(r); + assert(rl); - if (!ratelimit_configured(r)) + if (!ratelimit_configured(rl)) return true; ts = now(CLOCK_MONOTONIC); - if (r->begin <= 0 || - usec_sub_unsigned(ts, r->begin) > r->interval) { - r->begin = ts; /* Start a new time window */ - r->num = 1; /* Reset counter */ + if (rl->begin <= 0 || + usec_sub_unsigned(ts, rl->begin) > rl->interval) { + rl->begin = ts; /* Start a new time window */ + rl->num = 1; /* Reset counter */ return true; } - if (_unlikely_(r->num == UINT_MAX)) + if (_unlikely_(rl->num == UINT_MAX)) return false; - r->num++; - return r->num <= r->burst; + rl->num++; + return rl->num <= rl->burst; } -unsigned ratelimit_num_dropped(RateLimit *r) { - assert(r); +unsigned ratelimit_num_dropped(const RateLimit *rl) { + assert(rl); - if (r->num == UINT_MAX) /* overflow, return as special case */ + if (rl->num == UINT_MAX) /* overflow, return as special case */ return UINT_MAX; - return LESS_BY(r->num, r->burst); + return LESS_BY(rl->num, rl->burst); } usec_t ratelimit_end(const RateLimit *rl) { diff --git a/src/libnm-systemd-shared/src/basic/ratelimit.h b/src/libnm-systemd-shared/src/basic/ratelimit.h index 492ea3b48d..7801ef4270 100644 --- a/src/libnm-systemd-shared/src/basic/ratelimit.h +++ b/src/libnm-systemd-shared/src/basic/ratelimit.h @@ -18,13 +18,13 @@ static inline void ratelimit_reset(RateLimit *rl) { rl->num = rl->begin = 0; } -static inline bool ratelimit_configured(RateLimit *rl) { +static inline bool ratelimit_configured(const RateLimit *rl) { return rl->interval > 0 && rl->burst > 0; } -bool ratelimit_below(RateLimit *r); +bool ratelimit_below(RateLimit *rl); -unsigned ratelimit_num_dropped(RateLimit *r); +unsigned ratelimit_num_dropped(const RateLimit *rl); usec_t ratelimit_end(const RateLimit *rl); usec_t ratelimit_left(const RateLimit *rl); diff --git a/src/libnm-systemd-shared/src/basic/sha256.c b/src/libnm-systemd-shared/src/basic/sha256.c new file mode 100644 index 0000000000..f011695751 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/sha256.c @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "hexdecoct.h" +#include "macro.h" +#include "sha256.h" + +int sha256_fd(int fd, uint64_t max_size, uint8_t ret[static SHA256_DIGEST_SIZE]) { + struct sha256_ctx ctx; + uint64_t total_size = 0; + + sha256_init_ctx(&ctx); + + for (;;) { + uint8_t buffer[64 * 1024]; + ssize_t n; + + n = read(fd, buffer, sizeof(buffer)); + if (n < 0) + return -errno; + if (n == 0) + break; + + if (!INC_SAFE(&total_size, n) || total_size > max_size) + return -EFBIG; + + sha256_process_bytes(buffer, n, &ctx); + } + + sha256_finish_ctx(&ctx, ret); + return 0; +} + +int parse_sha256(const char *s, uint8_t ret[static SHA256_DIGEST_SIZE]) { + _cleanup_free_ uint8_t *data = NULL; + size_t size = 0; + int r; + + if (!sha256_is_valid(s)) + return -EINVAL; + + r = unhexmem_full(s, SHA256_DIGEST_SIZE * 2, false, (void**) &data, &size); + if (r < 0) + return r; + assert(size == SHA256_DIGEST_SIZE); + + memcpy(ret, data, size); + return 0; +} diff --git a/src/libnm-systemd-shared/src/basic/sha256.h b/src/libnm-systemd-shared/src/basic/sha256.h new file mode 100644 index 0000000000..95bac1bc1d --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/sha256.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#pragma once + +#include + +#include "sha256-fundamental.h" +#include "string-util.h" + +int sha256_fd(int fd, uint64_t max_size, uint8_t ret[static SHA256_DIGEST_SIZE]); + +int parse_sha256(const char *s, uint8_t res[static SHA256_DIGEST_SIZE]); + +static inline bool sha256_is_valid(const char *s) { + return s && in_charset(s, HEXDIGITS) && (strlen(s) == SHA256_DIGEST_SIZE * 2); +} diff --git a/src/libnm-systemd-shared/src/basic/signal-util.c b/src/libnm-systemd-shared/src/basic/signal-util.c index f8f4e509ad..32d37e68dd 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.c +++ b/src/libnm-systemd-shared/src/basic/signal-util.c @@ -14,11 +14,7 @@ #include "string-util.h" int reset_all_signal_handlers(void) { - static const struct sigaction sa = { - .sa_handler = SIG_DFL, - .sa_flags = SA_RESTART, - }; - int r = 0; + int ret = 0, r; for (int sig = 1; sig < _NSIG; sig++) { @@ -26,14 +22,14 @@ int reset_all_signal_handlers(void) { if (IN_SET(sig, SIGKILL, SIGSTOP)) continue; - /* On Linux the first two RT signals are reserved by - * glibc, and sigaction() will return EINVAL for them. */ - if (sigaction(sig, &sa, NULL) < 0) - if (errno != EINVAL && r >= 0) - r = -errno; + /* On Linux the first two RT signals are reserved by glibc, and sigaction() will return + * EINVAL for them. */ + r = RET_NERRNO(sigaction(sig, &sigaction_default, NULL)); + if (r != -EINVAL) + RET_GATHER(ret, r); } - return r; + return ret; } int reset_signal_mask(void) { @@ -57,10 +53,7 @@ int sigaction_many_internal(const struct sigaction *sa, ...) { if (sig == 0) continue; - if (sigaction(sig, sa, NULL) < 0) { - if (r >= 0) - r = -errno; - } + RET_GATHER(r, RET_NERRNO(sigaction(sig, sa, NULL))); } va_end(ap); @@ -87,7 +80,7 @@ static int sigset_add_many_ap(sigset_t *ss, va_list ap) { return r; } -int sigset_add_many(sigset_t *ss, ...) { +int sigset_add_many_internal(sigset_t *ss, ...) { va_list ap; int r; @@ -98,7 +91,7 @@ int sigset_add_many(sigset_t *ss, ...) { return r; } -int sigprocmask_many(int how, sigset_t *old, ...) { +int sigprocmask_many_internal(int how, sigset_t *old, ...) { va_list ap; sigset_t ss; int r; @@ -113,10 +106,7 @@ int sigprocmask_many(int how, sigset_t *old, ...) { if (r < 0) return r; - if (sigprocmask(how, &ss, old) < 0) - return -errno; - - return 0; + return RET_NERRNO(sigprocmask(how, &ss, old)); } static const char *const static_signal_table[] = { @@ -157,7 +147,7 @@ static const char *const static_signal_table[] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP(static_signal, int); -const char *signal_to_string(int signo) { +const char* signal_to_string(int signo) { static thread_local char buf[STRLEN("RTMIN+") + DECIMAL_STR_MAX(int)]; const char *name; @@ -274,7 +264,7 @@ int pop_pending_signal_internal(int sig, ...) { if (r < 0) return r; - r = sigtimedwait(&ss, NULL, &(struct timespec) { 0, 0 }); + r = sigtimedwait(&ss, NULL, &(const struct timespec) {}); if (r < 0) { if (errno == EAGAIN) return 0; @@ -301,3 +291,34 @@ void propagate_signal(int sig, siginfo_t *siginfo) { if (rt_tgsigqueueinfo(p, gettid(), sig, siginfo) < 0) assert_se(kill(p, sig) >= 0); } + +const struct sigaction sigaction_ignore = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, +}; + +const struct sigaction sigaction_default = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, +}; + +const struct sigaction sigaction_nop_nocldstop = { + .sa_handler = nop_signal_handler, + .sa_flags = SA_NOCLDSTOP|SA_RESTART, +}; + +int parse_signo(const char *s, int *ret) { + int sig, r; + + r = safe_atoi(s, &sig); + if (r < 0) + return r; + + if (!SIGNAL_VALID(sig)) + return -EINVAL; + + if (ret) + *ret = sig; + + return 0; +} diff --git a/src/libnm-systemd-shared/src/basic/signal-util.h b/src/libnm-systemd-shared/src/basic/signal-util.h index ad2ba841c6..dc2b9de147 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.h +++ b/src/libnm-systemd-shared/src/basic/signal-util.h @@ -12,29 +12,26 @@ int sigaction_many_internal(const struct sigaction *sa, ...); #define ignore_signals(...) \ sigaction_many_internal( \ - &(const struct sigaction) { \ - .sa_handler = SIG_IGN, \ - .sa_flags = SA_RESTART \ - }, \ + &sigaction_ignore, \ __VA_ARGS__, \ -1) #define default_signals(...) \ sigaction_many_internal( \ - &(const struct sigaction) { \ - .sa_handler = SIG_DFL, \ - .sa_flags = SA_RESTART \ - }, \ + &sigaction_default, \ __VA_ARGS__, \ -1) #define sigaction_many(sa, ...) \ sigaction_many_internal(sa, __VA_ARGS__, -1) -int sigset_add_many(sigset_t *ss, ...); -int sigprocmask_many(int how, sigset_t *old, ...); +int sigset_add_many_internal(sigset_t *ss, ...); +#define sigset_add_many(...) sigset_add_many_internal(__VA_ARGS__, -1) -const char *signal_to_string(int i) _const_; +int sigprocmask_many_internal(int how, sigset_t *ret_old_mask, ...); +#define sigprocmask_many(...) sigprocmask_many_internal(__VA_ARGS__, -1) + +const char* signal_to_string(int i) _const_; int signal_from_string(const char *s) _pure_; void nop_signal_handler(int sig); @@ -46,9 +43,10 @@ static inline void block_signals_reset(sigset_t *ss) { #define BLOCK_SIGNALS(...) \ _cleanup_(block_signals_reset) _unused_ sigset_t _saved_sigset = ({ \ sigset_t _t; \ - assert_se(sigprocmask_many(SIG_BLOCK, &_t, __VA_ARGS__, -1) >= 0); \ + assert_se(sigprocmask_many(SIG_BLOCK, &_t, __VA_ARGS__) >= 0); \ _t; \ }) +#define SIGNO_INVALID (-EINVAL) static inline bool SIGNAL_VALID(int signo) { return signo > 0 && signo < _NSIG; @@ -67,3 +65,9 @@ int pop_pending_signal_internal(int sig, ...); #define pop_pending_signal(...) pop_pending_signal_internal(__VA_ARGS__, -1) void propagate_signal(int sig, siginfo_t *siginfo); + +extern const struct sigaction sigaction_ignore; +extern const struct sigaction sigaction_default; +extern const struct sigaction sigaction_nop_nocldstop; + +int parse_signo(const char *s, int *ret); diff --git a/src/libnm-systemd-shared/src/basic/socket-util.c b/src/libnm-systemd-shared/src/basic/socket-util.c index 68e6afc67f..9190072f4e 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.c +++ b/src/libnm-systemd-shared/src/basic/socket-util.c @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include #include #include -#include #include #include #include @@ -20,7 +21,7 @@ #include "escape.h" #include "fd-util.h" #include "fileio.h" -#include "format-util.h" +#include "format-ifname.h" #include "io-util.h" #include "log.h" #include "memory-util.h" @@ -453,6 +454,7 @@ int sockaddr_pretty( assert(sa); assert(salen >= sizeof(sa->sa.sa_family)); + assert(ret); switch (sa->sa.sa_family) { @@ -633,7 +635,8 @@ int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **ret) int r; assert(sa); - assert(salen > sizeof(sa_family_t)); + assert(salen >= sizeof(sa_family_t)); + assert(ret); r = getnameinfo(sa, salen, host, sizeof(host), /* service= */ NULL, /* service_len= */ 0, IDN_FLAGS); if (r != 0) { @@ -647,15 +650,7 @@ int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **ret) return sockaddr_pretty(sa, salen, /* translate_ipv6= */ true, /* include_port= */ true, ret); } - if (ret) { - char *copy = strdup(host); - if (!copy) - return -ENOMEM; - - *ret = copy; - } - - return 0; + return strdup_to(ret, host); } static const char* const netlink_family_table[] = { @@ -980,6 +975,28 @@ int getpeerpidfd(int fd) { return pidfd; } +int getpeerpidref(int fd, PidRef *ret) { + int r; + + assert(fd >= 0); + assert(ret); + + int pidfd = getpeerpidfd(fd); + if (pidfd < 0) { + if (!ERRNO_IS_NEG_NOT_SUPPORTED(pidfd)) + return pidfd; + + struct ucred ucred; + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return pidref_set_pid(ret, ucred.pid); + } + + return pidref_set_pidfd_consume(ret, pidfd); +} + ssize_t send_many_fds_iov_sa( int transport_fd, int *fds_array, size_t n_fds_array, @@ -1117,14 +1134,10 @@ ssize_t receive_many_fds_iov( if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { size_t n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - fds_array = GREEDY_REALLOC(fds_array, n_fds_array + n); - if (!fds_array) { + if (!GREEDY_REALLOC_APPEND(fds_array, n_fds_array, CMSG_TYPED_DATA(cmsg, int), n)) { cmsg_close_all(&mh); return -ENOMEM; } - - memcpy(fds_array + n_fds_array, CMSG_TYPED_DATA(cmsg, int), sizeof(int) * n); - n_fds_array += n; } if (n_fds_array == 0) { @@ -1456,18 +1469,22 @@ int socket_bind_to_ifindex(int fd, int ifindex) { ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags) { ssize_t n; - /* A wrapper around recvmsg() that checks for MSG_CTRUNC, and turns it into an error, in a reasonably - * safe way, closing any SCM_RIGHTS fds in the error path. + /* A wrapper around recvmsg() that checks for MSG_CTRUNC and MSG_TRUNC, and turns them into an error, + * in a reasonably safe way, closing any received fds in the error path. * * Note that unlike our usual coding style this might modify *msg on failure. */ + assert(sockfd >= 0); + assert(msg); + n = recvmsg(sockfd, msg, flags); if (n < 0) return -errno; - if (FLAGS_SET(msg->msg_flags, MSG_CTRUNC)) { + if (FLAGS_SET(msg->msg_flags, MSG_CTRUNC) || + (!FLAGS_SET(flags, MSG_PEEK) && FLAGS_SET(msg->msg_flags, MSG_TRUNC))) { cmsg_close_all(msg); - return -EXFULL; /* a recognizable error code */ + return FLAGS_SET(msg->msg_flags, MSG_CTRUNC) ? -ECHRNG : -EXFULL; } return n; @@ -1766,14 +1783,48 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { int vsock_get_local_cid(unsigned *ret) { _cleanup_close_ int vsock_fd = -EBADF; - assert(ret); - vsock_fd = open("/dev/vsock", O_RDONLY|O_CLOEXEC); if (vsock_fd < 0) return log_debug_errno(errno, "Failed to open /dev/vsock: %m"); - if (ioctl(vsock_fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, ret) < 0) + unsigned tmp; + if (ioctl(vsock_fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, ret ?: &tmp) < 0) return log_debug_errno(errno, "Failed to query local AF_VSOCK CID: %m"); return 0; } + +int netlink_socket_get_multicast_groups(int fd, size_t *ret_len, uint32_t **ret_groups) { + _cleanup_free_ uint32_t *groups = NULL; + socklen_t len = 0, old_len; + + assert(fd >= 0); + + /* This returns ENOPROTOOPT if the kernel is older than 4.2. */ + + if (getsockopt(fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, NULL, &len) < 0) + return -errno; + + if (len == 0) + goto finalize; + + groups = new0(uint32_t, len); + if (!groups) + return -ENOMEM; + + old_len = len; + + if (getsockopt(fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, groups, &len) < 0) + return -errno; + + if (old_len != len) + return -EIO; + +finalize: + if (ret_len) + *ret_len = len; + if (ret_groups) + *ret_groups = TAKE_PTR(groups); + + return 0; +} diff --git a/src/libnm-systemd-shared/src/basic/socket-util.h b/src/libnm-systemd-shared/src/basic/socket-util.h index c784125ccb..e21a4427f0 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.h +++ b/src/libnm-systemd-shared/src/basic/socket-util.h @@ -2,15 +2,16 @@ #pragma once #include -#include #include #include #include +#include +#include /* linux/vms_sockets.h requires 'struct sockaddr' */ +#include #include #include #include #include -#include #include #include @@ -19,6 +20,7 @@ #include "macro.h" #include "missing_network.h" #include "missing_socket.h" +#include "pidref.h" #include "sparse-endian.h" union sockaddr_union { @@ -28,7 +30,7 @@ union sockaddr_union { /* The libc provided version that allocates "enough room" for every protocol */ struct sockaddr_storage storage; - /* Protoctol-specific implementations */ + /* Protocol-specific implementations */ struct sockaddr_in in; struct sockaddr_in6 in6; struct sockaddr_un un; @@ -153,6 +155,7 @@ int getpeercred(int fd, struct ucred *ucred); int getpeersec(int fd, char **ret); int getpeergroups(int fd, gid_t **ret); int getpeerpidfd(int fd); +int getpeerpidref(int fd, PidRef *ret); ssize_t send_many_fds_iov_sa( int transport_fd, @@ -329,7 +332,7 @@ struct timespec_large { /* glibc duplicates timespec/timeval on certain 32-bit arches, once in 32-bit and once in 64-bit. * See __convert_scm_timestamps() in glibc source code. Hence, we need additional buffer space for them - * to prevent from recvmsg_safe() returning -EXFULL. */ + * to prevent truncating control msg (recvmsg() MSG_CTRUNC). */ #define CMSG_SPACE_TIMEVAL \ ((sizeof(struct timeval) == sizeof(struct timeval_large)) ? \ CMSG_SPACE(sizeof(struct timeval)) : \ @@ -389,10 +392,12 @@ int socket_address_parse_unix(SocketAddress *ret_address, const char *s); int socket_address_parse_vsock(SocketAddress *ret_address, const char *s); /* libc's SOMAXCONN is defined to 128 or 4096 (at least on glibc). But actually, the value can be much - * larger. In our codebase we want to set it to the max usually, since noawadays socket memory is properly + * larger. In our codebase we want to set it to the max usually, since nowadays socket memory is properly * tracked by memcg, and hence we don't need to enforce extra limits here. Moreover, the kernel caps it to * /proc/sys/net/core/somaxconn anyway, thus by setting this to unbounded we just make that sysctl file * authoritative. */ #define SOMAXCONN_DELUXE INT_MAX int vsock_get_local_cid(unsigned *ret); + +int netlink_socket_get_multicast_groups(int fd, size_t *ret_len, uint32_t **ret_groups); diff --git a/src/libnm-systemd-shared/src/basic/stat-util.c b/src/libnm-systemd-shared/src/basic/stat-util.c index 51715668fe..2181ee2df5 100644 --- a/src/libnm-systemd-shared/src/basic/stat-util.c +++ b/src/libnm-systemd-shared/src/basic/stat-util.c @@ -20,48 +20,136 @@ #include "missing_fs.h" #include "missing_magic.h" #include "missing_syscall.h" +#include "mountpoint-util.h" #include "nulstr-util.h" #include "parse-util.h" #include "stat-util.h" #include "string-util.h" -int is_symlink(const char *path) { - struct stat info; +static int verify_stat_at( + int fd, + const char *path, + bool follow, + int (*verify_func)(const struct stat *st), + bool verify) { - assert(path); - - if (lstat(path, &info) < 0) - return -errno; - - return !!S_ISLNK(info.st_mode); -} - -int is_dir_full(int atfd, const char* path, bool follow) { struct stat st; int r; - assert(atfd >= 0 || atfd == AT_FDCWD); - assert(atfd >= 0 || path); + assert(fd >= 0 || fd == AT_FDCWD); + assert(!isempty(path) || !follow); + assert(verify_func); - if (path) - r = fstatat(atfd, path, &st, follow ? 0 : AT_SYMLINK_NOFOLLOW); - else - r = fstat(atfd, &st); - if (r < 0) + if (fstatat(fd, strempty(path), &st, + (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0) return -errno; - return !!S_ISDIR(st.st_mode); + r = verify_func(&st); + return verify ? r : r >= 0; +} + +int stat_verify_regular(const struct stat *st) { + assert(st); + + /* Checks whether the specified stat() structure refers to a regular file. If not returns an + * appropriate error code. */ + + if (S_ISDIR(st->st_mode)) + return -EISDIR; + + if (S_ISLNK(st->st_mode)) + return -ELOOP; + + if (!S_ISREG(st->st_mode)) + return -EBADFD; + + return 0; +} + +int verify_regular_at(int fd, const char *path, bool follow) { + return verify_stat_at(fd, path, follow, stat_verify_regular, true); +} + +int fd_verify_regular(int fd) { + assert(fd >= 0); + return verify_regular_at(fd, NULL, false); +} + +int stat_verify_directory(const struct stat *st) { + assert(st); + + if (S_ISLNK(st->st_mode)) + return -ELOOP; + + if (!S_ISDIR(st->st_mode)) + return -ENOTDIR; + + return 0; +} + +int fd_verify_directory(int fd) { + assert(fd >= 0); + return verify_stat_at(fd, NULL, false, stat_verify_directory, true); +} + +int is_dir_at(int fd, const char *path, bool follow) { + return verify_stat_at(fd, path, follow, stat_verify_directory, false); +} + +int is_dir(const char *path, bool follow) { + assert(!isempty(path)); + return is_dir_at(AT_FDCWD, path, follow); +} + +int stat_verify_symlink(const struct stat *st) { + assert(st); + + if (S_ISDIR(st->st_mode)) + return -EISDIR; + + if (!S_ISLNK(st->st_mode)) + return -ENOLINK; + + return 0; +} + +int is_symlink(const char *path) { + assert(!isempty(path)); + return verify_stat_at(AT_FDCWD, path, false, stat_verify_symlink, false); +} + +int stat_verify_linked(const struct stat *st) { + assert(st); + + if (st->st_nlink <= 0) + return -EIDRM; /* recognizable error. */ + + return 0; +} + +int fd_verify_linked(int fd) { + assert(fd >= 0); + return verify_stat_at(fd, NULL, false, stat_verify_linked, true); +} + +int stat_verify_device_node(const struct stat *st) { + assert(st); + + if (S_ISLNK(st->st_mode)) + return -ELOOP; + + if (S_ISDIR(st->st_mode)) + return -EISDIR; + + if (!S_ISBLK(st->st_mode) && !S_ISCHR(st->st_mode)) + return -ENOTTY; + + return 0; } int is_device_node(const char *path) { - struct stat info; - - assert(path); - - if (lstat(path, &info) < 0) - return -errno; - - return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode)); + assert(!isempty(path)); + return verify_stat_at(AT_FDCWD, path, false, stat_verify_device_node, false); } int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) { @@ -69,25 +157,9 @@ int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) struct dirent *buf; size_t m; - if (path) { - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - - fd = openat(dir_fd, path, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (fd < 0) - return -errno; - } else if (dir_fd == AT_FDCWD) { - fd = open(".", O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (fd < 0) - return -errno; - } else { - /* Note that DUPing is not enough, as the internal pointer would still be shared and moved - * getedents64(). */ - assert(dir_fd >= 0); - - fd = fd_reopen(dir_fd, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (fd < 0) - return fd; - } + fd = xopenat(dir_fd, path, O_DIRECTORY|O_CLOEXEC); + if (fd < 0) + return fd; /* Allocate space for at least 3 full dirents, since every dir has at least two entries ("." + * ".."), and only once we have seen if there's a third we know whether the dir is empty or not. If @@ -142,7 +214,7 @@ int null_or_empty_path_with_root(const char *fn, const char *root) { * When looking under root_dir, we can't expect /dev/ to be mounted, * so let's see if the path is a (possibly dangling) symlink to /dev/null. */ - if (path_equal_ptr(path_startswith(fn, root ?: "/"), "dev/null")) + if (path_equal(path_startswith(fn, root ?: "/"), "dev/null")) return true; r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st); @@ -152,7 +224,7 @@ int null_or_empty_path_with_root(const char *fn, const char *root) { return null_or_empty(&st); } -static int fd_is_read_only_fs(int fd) { +int fd_is_read_only_fs(int fd) { struct statvfs st; assert(fd >= 0); @@ -184,18 +256,103 @@ int path_is_read_only_fs(const char *path) { } int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags) { - struct stat a, b; + struct stat sta, stb; + int r; assert(fda >= 0 || fda == AT_FDCWD); assert(fdb >= 0 || fdb == AT_FDCWD); + assert((flags & ~(AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT)) == 0); - if (fstatat(fda, strempty(filea), &a, flags) < 0) - return log_debug_errno(errno, "Cannot stat %s: %m", filea); + /* Refuse an unset filea or fileb early unless AT_EMPTY_PATH is set */ + if ((isempty(filea) || isempty(fileb)) && !FLAGS_SET(flags, AT_EMPTY_PATH)) + return -EINVAL; - if (fstatat(fdb, strempty(fileb), &b, flags) < 0) - return log_debug_errno(errno, "Cannot stat %s: %m", fileb); + /* Shortcut: comparing the same fd with itself means we can return true */ + if (fda >= 0 && fda == fdb && isempty(filea) && isempty(fileb) && FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW)) + return true; - return stat_inode_same(&a, &b); + _cleanup_close_ int pin_a = -EBADF, pin_b = -EBADF; + if (!FLAGS_SET(flags, AT_NO_AUTOMOUNT)) { + /* Let's try to use the name_to_handle_at() AT_HANDLE_FID API to identify identical + * inodes. We have to issue multiple calls on the same file for that (first, to acquire the + * FID, and then to check if .st_dev is actually the same). Hence let's pin the inode in + * between via O_PATH, unless we already have an fd for it. */ + + if (!isempty(filea)) { + pin_a = openat(fda, filea, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW) ? O_NOFOLLOW : 0)); + if (pin_a < 0) + return -errno; + + fda = pin_a; + filea = NULL; + flags |= AT_EMPTY_PATH; + } + + if (!isempty(fileb)) { + pin_b = openat(fdb, fileb, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW) ? O_NOFOLLOW : 0)); + if (pin_b < 0) + return -errno; + + fdb = pin_b; + fileb = NULL; + flags |= AT_EMPTY_PATH; + } + + int ntha_flags = (flags & AT_EMPTY_PATH) | (FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW) ? 0 : AT_SYMLINK_FOLLOW); + _cleanup_free_ struct file_handle *ha = NULL, *hb = NULL; + int mntida = -1, mntidb = -1; + + r = name_to_handle_at_try_fid( + fda, + filea, + &ha, + &mntida, + ntha_flags); + if (r < 0) { + if (is_name_to_handle_at_fatal_error(r)) + return r; + + goto fallback; + } + + r = name_to_handle_at_try_fid( + fdb, + fileb, + &hb, + &mntidb, + ntha_flags); + if (r < 0) { + if (is_name_to_handle_at_fatal_error(r)) + return r; + + goto fallback; + } + + /* Now compare the two file handles */ + if (!file_handle_equal(ha, hb)) + return false; + + /* If the file handles are the same and they come from the same mount ID? Great, then we are + * good, they are definitely the same */ + if (mntida == mntidb) + return true; + + /* File handles are the same, they are not on the same mount id. This might either be because + * they are on two entirely different file systems, that just happen to have the same FIDs + * (because they originally where created off the same disk images), or it could be because + * they are located on two distinct bind mounts of the same fs. To check that, let's look at + * .st_rdev of the inode. We simply reuse the fallback codepath for that, since it checks + * exactly that (it checks slightly more, but we don't care.) */ + } + +fallback: + if (fstatat(fda, strempty(filea), &sta, flags) < 0) + return log_debug_errno(errno, "Cannot stat %s: %m", strna(filea)); + + if (fstatat(fdb, strempty(fileb), &stb, flags) < 0) + return log_debug_errno(errno, "Cannot stat %s: %m", strna(fileb)); + + return stat_inode_same(&sta, &stb); } bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) { @@ -260,70 +417,6 @@ int path_is_network_fs(const char *path) { return is_network_fs(&s); } -int stat_verify_regular(const struct stat *st) { - assert(st); - - /* Checks whether the specified stat() structure refers to a regular file. If not returns an appropriate error - * code. */ - - if (S_ISDIR(st->st_mode)) - return -EISDIR; - - if (S_ISLNK(st->st_mode)) - return -ELOOP; - - if (!S_ISREG(st->st_mode)) - return -EBADFD; - - return 0; -} - -int fd_verify_regular(int fd) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - return stat_verify_regular(&st); -} - -int verify_regular_at(int dir_fd, const char *path, bool follow) { - struct stat st; - - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(path); - - if (fstatat(dir_fd, path, &st, (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0) - return -errno; - - return stat_verify_regular(&st); -} - -int stat_verify_directory(const struct stat *st) { - assert(st); - - if (S_ISLNK(st->st_mode)) - return -ELOOP; - - if (!S_ISDIR(st->st_mode)) - return -ENOTDIR; - - return 0; -} - -int fd_verify_directory(int fd) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - return stat_verify_directory(&st); -} - int proc_mounted(void) { int r; @@ -341,8 +434,7 @@ bool stat_inode_same(const struct stat *a, const struct stat *b) { /* Returns if the specified stat structure references the same (though possibly modified) inode. Does * a thorough check, comparing inode nr, backing device and if the inode is still of the same type. */ - return a && b && - (a->st_mode & S_IFMT) != 0 && /* We use the check for .st_mode if the structure was ever initialized */ + return stat_is_set(a) && stat_is_set(b) && ((a->st_mode ^ b->st_mode) & S_IFMT) == 0 && /* same inode type */ a->st_dev == b->st_dev && a->st_ino == b->st_ino; @@ -370,9 +462,8 @@ bool statx_inode_same(const struct statx *a, const struct statx *b) { /* Same as stat_inode_same() but for struct statx */ - return a && b && + return statx_is_set(a) && statx_is_set(b) && FLAGS_SET(a->stx_mask, STATX_TYPE|STATX_INO) && FLAGS_SET(b->stx_mask, STATX_TYPE|STATX_INO) && - (a->stx_mode & S_IFMT) != 0 && ((a->stx_mode ^ b->stx_mode) & S_IFMT) == 0 && a->stx_dev_major == b->stx_dev_major && a->stx_dev_minor == b->stx_dev_minor && @@ -380,7 +471,7 @@ bool statx_inode_same(const struct statx *a, const struct statx *b) { } bool statx_mount_same(const struct new_statx *a, const struct new_statx *b) { - if (!a || !b) + if (!new_statx_is_set(a) || !new_statx_is_set(b)) return false; /* if we have the mount ID, that's all we need */ @@ -468,7 +559,7 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(ret); - fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0); + fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY); if (fd < 0) return fd; @@ -514,6 +605,8 @@ const char* inode_type_to_string(mode_t m) { return "sock"; } + /* Note anonymous inodes in the kernel will have a zero type. Hence fstat() of an eventfd() will + * return an .st_mode where we'll return NULL here! */ return NULL; } diff --git a/src/libnm-systemd-shared/src/basic/stat-util.h b/src/libnm-systemd-shared/src/basic/stat-util.h index dc11a85f62..7556f8f59a 100644 --- a/src/libnm-systemd-shared/src/basic/stat-util.h +++ b/src/libnm-systemd-shared/src/basic/stat-util.h @@ -9,19 +9,28 @@ #include #include +#include "fs-util.h" #include "macro.h" #include "missing_stat.h" #include "siphash24.h" #include "time-util.h" +int stat_verify_regular(const struct stat *st); +int verify_regular_at(int fd, const char *path, bool follow); +int fd_verify_regular(int fd); + +int stat_verify_directory(const struct stat *st); +int fd_verify_directory(int fd); +int is_dir_at(int fd, const char *path, bool follow); +int is_dir(const char *path, bool follow); + +int stat_verify_symlink(const struct stat *st); int is_symlink(const char *path); -int is_dir_full(int atfd, const char *fname, bool follow); -static inline int is_dir(const char *path, bool follow) { - return is_dir_full(AT_FDCWD, path, follow); -} -static inline int is_dir_fd(int fd) { - return is_dir_full(fd, NULL, false); -} + +int stat_verify_linked(const struct stat *st); +int fd_verify_linked(int fd); + +int stat_verify_device_node(const struct stat *st); int is_device_node(const char *path); int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup); @@ -36,13 +45,16 @@ static inline int null_or_empty_path(const char *fn) { return null_or_empty_path_with_root(fn, NULL); } +int fd_is_read_only_fs(int fd); int path_is_read_only_fs(const char *path); int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags); - static inline int inode_same(const char *filea, const char *fileb, int flags) { return inode_same_at(AT_FDCWD, filea, AT_FDCWD, fileb, flags); } +static inline int fd_inode_same(int fda, int fdb) { + return inode_same_at(fda, NULL, fdb, NULL, AT_EMPTY_PATH); +} /* The .f_type field of struct statfs is really weird defined on * different archs. Let's give its type a name. */ @@ -72,13 +84,6 @@ int path_is_network_fs(const char *path); */ #define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b) -int stat_verify_regular(const struct stat *st); -int fd_verify_regular(int fd); -int verify_regular_at(int dir_fd, const char *path, bool follow); - -int stat_verify_directory(const struct stat *st); -int fd_verify_directory(int fd); - int proc_mounted(void); bool stat_inode_same(const struct stat *a, const struct stat *b); @@ -123,3 +128,18 @@ extern const struct hash_ops inode_hash_ops; const char* inode_type_to_string(mode_t m); mode_t inode_type_from_string(const char *s); + +/* Macros that check whether the stat/statx structures have been initialized already. For "struct stat" we + * use a check for .st_dev being non-zero, since the kernel unconditionally fills that in, mapping the file + * to its originating superblock, regardless if the fs is block based or virtual (we also check for .st_mode + * being MODE_INVALID, since we use that as an invalid marker for separate mode_t fields). For "struct statx" + * we use the .stx_mask field, which must be non-zero if any of the fields have already been initialized. */ +static inline bool stat_is_set(const struct stat *st) { + return st && st->st_dev != 0 && st->st_mode != MODE_INVALID; +} +static inline bool statx_is_set(const struct statx *sx) { + return sx && sx->stx_mask != 0; +} +static inline bool new_statx_is_set(const struct new_statx *sx) { + return sx && sx->stx_mask != 0; +} diff --git a/src/libnm-systemd-shared/src/basic/stdio-util.h b/src/libnm-systemd-shared/src/basic/stdio-util.h index 4e93ac90c9..0a2239d022 100644 --- a/src/libnm-systemd-shared/src/basic/stdio-util.h +++ b/src/libnm-systemd-shared/src/basic/stdio-util.h @@ -9,14 +9,12 @@ #include "macro.h" _printf_(3, 4) -static inline char *snprintf_ok(char *buf, size_t len, const char *format, ...) { +static inline char* snprintf_ok(char *buf, size_t len, const char *format, ...) { va_list ap; int r; va_start(ap, format); - DISABLE_WARNING_FORMAT_NONLITERAL; r = vsnprintf(buf, len, format, ap); - REENABLE_WARNING; va_end(ap); return r >= 0 && (size_t) r < len ? buf : NULL; diff --git a/src/libnm-systemd-shared/src/basic/string-table.h b/src/libnm-systemd-shared/src/basic/string-table.h index 3be70dfade..83891eeb73 100644 --- a/src/libnm-systemd-shared/src/basic/string-table.h +++ b/src/libnm-systemd-shared/src/basic/string-table.h @@ -15,7 +15,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k /* For basic lookup tables with strictly enumerated entries */ #define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ - scope const char *name##_to_string(type i) { \ + scope const char* name##_to_string(type i) { \ if (i < 0 || i >= (type) ELEMENTSOF(name##_table)) \ return NULL; \ return name##_table[i]; \ @@ -47,10 +47,8 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k s = strdup(name##_table[i]); \ if (!s) \ return -ENOMEM; \ - } else { \ - if (asprintf(&s, "%i", i) < 0) \ - return -ENOMEM; \ - } \ + } else if (asprintf(&s, "%i", i) < 0) \ + return -ENOMEM; \ *str = s; \ return 0; \ } @@ -103,7 +101,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,static) #define DUMP_STRING_TABLE(name,type,max) \ - do { \ + ({ \ flockfile(stdout); \ for (type _k = 0; _k < (max); _k++) { \ const char *_t; \ @@ -114,4 +112,5 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k fputc_unlocked('\n', stdout); \ } \ funlockfile(stdout); \ - } while (false) + 0; \ + }) diff --git a/src/libnm-systemd-shared/src/basic/string-util.c b/src/libnm-systemd-shared/src/basic/string-util.c index 2aac588118..b2919164d6 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.c +++ b/src/libnm-systemd-shared/src/basic/string-util.c @@ -11,6 +11,7 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" +#include "glyph-util.h" #include "gunicode.h" #include "locale-util.h" #include "macro.h" @@ -23,40 +24,29 @@ #include "utf8.h" char* first_word(const char *s, const char *word) { - size_t sl, wl; - const char *p; - assert(s); assert(word); - /* Checks if the string starts with the specified word, either - * followed by NUL or by whitespace. Returns a pointer to the - * NUL or the first character after the whitespace. */ + /* Checks if the string starts with the specified word, either followed by NUL or by whitespace. + * Returns a pointer to the NUL or the first character after the whitespace. */ - sl = strlen(s); - wl = strlen(word); - - if (sl < wl) - return NULL; - - if (wl == 0) + if (isempty(word)) return (char*) s; - if (memcmp(s, word, wl) != 0) + const char *p = startswith(s, word); + if (!p) return NULL; - - p = s + wl; - if (*p == 0) + if (*p == '\0') return (char*) p; - if (!strchr(WHITESPACE, *p)) + const char *nw = skip_leading_chars(p, WHITESPACE); + if (p == nw) return NULL; - p += strspn(p, WHITESPACE); - return (char*) p; + return (char*) nw; } -char *strnappend(const char *s, const char *suffix, size_t b) { +char* strnappend(const char *s, const char *suffix, size_t b) { size_t a; char *r; @@ -87,39 +77,7 @@ char *strnappend(const char *s, const char *suffix, size_t b) { return r; } -char *strjoin_real(const char *x, ...) { - va_list ap; - size_t l = 1; - char *r, *p; - - va_start(ap, x); - for (const char *t = x; t; t = va_arg(ap, const char *)) { - size_t n; - - n = strlen(t); - if (n > SIZE_MAX - l) { - va_end(ap); - return NULL; - } - l += n; - } - va_end(ap); - - p = r = new(char, l); - if (!r) - return NULL; - - va_start(ap, x); - for (const char *t = x; t; t = va_arg(ap, const char *)) - p = stpcpy(p, t); - va_end(ap); - - *p = 0; - - return r; -} - -char *strstrip(char *s) { +char* strstrip(char *s) { if (!s) return NULL; @@ -128,7 +86,7 @@ char *strstrip(char *s) { return delete_trailing_chars(skip_leading_chars(s, WHITESPACE), WHITESPACE); } -char *delete_chars(char *s, const char *bad) { +char* delete_chars(char *s, const char *bad) { char *f, *t; /* Drops all specified bad characters, regardless where in the string */ @@ -151,7 +109,7 @@ char *delete_chars(char *s, const char *bad) { return s; } -char *delete_trailing_chars(char *s, const char *bad) { +char* delete_trailing_chars(char *s, const char *bad) { char *c = s; /* Drops all specified bad characters, at the end of the string */ @@ -171,7 +129,7 @@ char *delete_trailing_chars(char *s, const char *bad) { return s; } -char *truncate_nl_full(char *s, size_t *ret_len) { +char* truncate_nl_full(char *s, size_t *ret_len) { size_t n; assert(s); @@ -199,7 +157,7 @@ char ascii_toupper(char x) { return x; } -char *ascii_strlower(char *t) { +char* ascii_strlower(char *t) { assert(t); for (char *p = t; *p; p++) @@ -208,7 +166,7 @@ char *ascii_strlower(char *t) { return t; } -char *ascii_strupper(char *t) { +char* ascii_strupper(char *t) { assert(t); for (char *p = t; *p; p++) @@ -217,7 +175,7 @@ char *ascii_strupper(char *t) { return t; } -char *ascii_strlower_n(char *t, size_t n) { +char* ascii_strlower_n(char *t, size_t n) { if (n <= 0) return t; @@ -282,16 +240,9 @@ bool string_has_cc(const char *p, const char *ok) { } static int write_ellipsis(char *buf, bool unicode) { - if (unicode || is_locale_utf8()) { - buf[0] = 0xe2; /* tri-dot ellipsis: … */ - buf[1] = 0x80; - buf[2] = 0xa6; - } else { - buf[0] = '.'; - buf[1] = '.'; - buf[2] = '.'; - } - + const char *s = special_glyph_full(SPECIAL_GLYPH_ELLIPSIS, unicode); + assert(strlen(s) == 3); + memcpy(buf, s, 3); return 3; } @@ -398,8 +349,7 @@ static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_le x = ((new_length - need_space) * percent + 50) / 100; assert(x <= new_length - need_space); - memcpy(t, s, x); - write_ellipsis(t + x, false); + write_ellipsis(mempcpy(t, s, x), /* unicode = */ false); suffix_len = new_length - x - need_space; memcpy(t + x + 3, s + old_length - suffix_len, suffix_len); *(t + x + 3 + suffix_len) = '\0'; @@ -407,7 +357,7 @@ static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_le return t; } -char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { +char* ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { size_t x, k, len, len2; const char *i, *j; int r; @@ -520,13 +470,8 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne if (!e) return NULL; - /* - printf("old_length=%zu new_length=%zu x=%zu len=%zu len2=%zu k=%zu\n", - old_length, new_length, x, len, len2, k); - */ - memcpy_safe(e, s, len); - write_ellipsis(e + len, true); + write_ellipsis(e + len, /* unicode = */ true); char *dst = e + len + 3; @@ -547,7 +492,7 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne return e; } -char *cellescape(char *buf, size_t len, const char *s) { +char* cellescape(char *buf, size_t len, const char *s) { /* Escape and ellipsize s into buffer buf of size len. Only non-control ASCII * characters are copied as they are, everything else is escaped. The result * is different then if escaping and ellipsization was performed in two @@ -562,7 +507,9 @@ char *cellescape(char *buf, size_t len, const char *s) { size_t i = 0, last_char_width[4] = {}, k = 0; + assert(buf); assert(len > 0); /* at least a terminating NUL */ + assert(s); for (;;) { char four[4]; @@ -603,7 +550,7 @@ char *cellescape(char *buf, size_t len, const char *s) { } if (i + 4 <= len) /* yay, enough space */ - i += write_ellipsis(buf + i, false); + i += write_ellipsis(buf + i, /* unicode = */ false); else if (i + 3 <= len) { /* only space for ".." */ buf[i++] = '.'; buf[i++] = '.'; @@ -612,7 +559,7 @@ char *cellescape(char *buf, size_t len, const char *s) { else assert(i + 1 <= len); - done: +done: buf[i] = '\0'; return buf; } @@ -620,6 +567,9 @@ char *cellescape(char *buf, size_t len, const char *s) { char* strshorten(char *s, size_t l) { assert(s); + if (l >= SIZE_MAX-1) /* Would not change anything */ + return s; + if (strnlen(s, l+1) > l) s[l] = 0; @@ -648,7 +598,7 @@ int strgrowpad0(char **s, size_t l) { return 0; } -char *strreplace(const char *text, const char *old_string, const char *new_string) { +char* strreplace(const char *text, const char *old_string, const char *new_string) { size_t l, old_len, new_len; char *t, *ret = NULL; const char *f; @@ -710,13 +660,14 @@ static void advance_offsets( shift[1] += size; } -char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { +char* strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { const char *begin = NULL; enum { STATE_OTHER, STATE_ESCAPE, STATE_CSI, - STATE_CSO, + STATE_OSC, + STATE_OSC_CLOSING, } state = STATE_OTHER; _cleanup_(memstream_done) MemStream m = {}; size_t isz, shift[2] = {}, n_carriage_returns = 0; @@ -729,7 +680,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { * * 1. Replaces TABs by 8 spaces * 2. Strips ANSI color sequences (a subset of CSI), i.e. ESC '[' … 'm' sequences - * 3. Strips ANSI operating system sequences (CSO), i.e. ESC ']' … BEL sequences + * 3. Strips ANSI operating system sequences (OSC), i.e. ESC ']' … ST sequences * 4. Strip trailing \r characters (since they would "move the cursor", but have no * other effect). * @@ -737,7 +688,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { * are any other special characters. Truncated ANSI sequences are left-as is too. This call is * supposed to suppress the most basic formatting noise, but nothing else. * - * Why care for CSO sequences? Well, to undo what terminal_urlify() and friends generate. */ + * Why care for OSC sequences? Well, to undo what terminal_urlify() and friends generate. */ isz = _isz ? *_isz : strlen(*ibuf); @@ -749,10 +700,12 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { for (const char *i = *ibuf; i < *ibuf + isz + 1; i++) { + bool eot = i >= *ibuf + isz; + switch (state) { case STATE_OTHER: - if (i >= *ibuf + isz) /* EOT */ + if (eot) break; if (*i == '\r') { @@ -777,15 +730,15 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { case STATE_ESCAPE: assert(n_carriage_returns == 0); - if (i >= *ibuf + isz) { /* EOT */ + if (eot) { fputc('\x1B', f); advance_offsets(i - *ibuf, highlight, shift, 1); break; } else if (*i == '[') { /* ANSI CSI */ state = STATE_CSI; begin = i + 1; - } else if (*i == ']') { /* ANSI CSO */ - state = STATE_CSO; + } else if (*i == ']') { /* ANSI OSC */ + state = STATE_OSC; begin = i + 1; } else { fputc('\x1B', f); @@ -799,8 +752,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { case STATE_CSI: assert(n_carriage_returns == 0); - if (i >= *ibuf + isz || /* EOT … */ - !strchr("01234567890;m", *i)) { /* … or invalid chars in sequence */ + if (eot || !strchr("01234567890;m", *i)) { /* EOT or invalid chars in sequence */ fputc('\x1B', f); fputc('[', f); advance_offsets(i - *ibuf, highlight, shift, 2); @@ -811,17 +763,33 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { break; - case STATE_CSO: + case STATE_OSC: assert(n_carriage_returns == 0); - if (i >= *ibuf + isz || /* EOT … */ - (*i != '\a' && (uint8_t) *i < 32U) || (uint8_t) *i > 126U) { /* … or invalid chars in sequence */ + /* There are three kinds of OSC terminators: \x07, \x1b\x5c or \x9c. We only support + * the first two, because the last one is a valid UTF-8 codepoint and hence creates + * an ambiguity (many Terminal emulators refuse to support it as well). */ + if (eot || (!IN_SET(*i, '\x07', '\x1b') && !osc_char_is_valid(*i))) { /* EOT or invalid chars in sequence */ fputc('\x1B', f); fputc(']', f); advance_offsets(i - *ibuf, highlight, shift, 2); state = STATE_OTHER; i = begin-1; - } else if (*i == '\a') + } else if (*i == '\x07') /* Single character ST */ + state = STATE_OTHER; + else if (*i == '\x1B') + state = STATE_OSC_CLOSING; + + break; + + case STATE_OSC_CLOSING: + if (eot || *i != '\x5c') { /* EOT or incomplete two-byte ST in sequence */ + fputc('\x1B', f); + fputc(']', f); + advance_offsets(i - *ibuf, highlight, shift, 2); + state = STATE_OTHER; + i = begin-1; + } else if (*i == '\x5c') state = STATE_OTHER; break; @@ -842,13 +810,15 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { return *ibuf; } -char *strextend_with_separator_internal(char **x, const char *separator, ...) { +char* strextend_with_separator_internal(char **x, const char *separator, ...) { + _cleanup_free_ char *buffer = NULL; size_t f, l, l_separator; bool need_separator; char *nr, *p; va_list ap; - assert(x); + if (!x) + x = &buffer; l = f = strlen_ptr(*x); @@ -856,13 +826,14 @@ char *strextend_with_separator_internal(char **x, const char *separator, ...) { l_separator = strlen_ptr(separator); va_start(ap, separator); - for (;;) { - const char *t; + for (const char *t;;) { size_t n; t = va_arg(ap, const char *); if (!t) break; + if (t == POINTER_MAX) + continue; n = strlen(t); @@ -895,6 +866,8 @@ char *strextend_with_separator_internal(char **x, const char *separator, ...) { t = va_arg(ap, const char *); if (!t) break; + if (t == POINTER_MAX) + continue; if (need_separator && separator) p = stpcpy(p, separator); @@ -906,9 +879,13 @@ char *strextend_with_separator_internal(char **x, const char *separator, ...) { va_end(ap); assert(p == nr + l); - *p = 0; + /* If no buffer to extend was passed in return the start of the buffer */ + if (buffer) + return TAKE_PTR(buffer); + + /* Otherwise we extended the buffer: return the end */ return p; } @@ -993,12 +970,12 @@ int strextendf_with_separator(char **x, const char *separator, const char *forma return 0; oom: - /* truncate the bytes added after the first vsnprintf() attempt again */ + /* truncate the bytes added after memcpy_safe() again */ (*x)[m] = 0; return -ENOMEM; } -char *strextendn(char **x, const char *s, size_t l) { +char* strextendn(char **x, const char *s, size_t l) { assert(x); assert(s || l == 0); @@ -1025,7 +1002,7 @@ char *strextendn(char **x, const char *s, size_t l) { return *x; } -char *strrep(const char *s, unsigned n) { +char* strrep(const char *s, unsigned n) { char *r, *p; size_t l; @@ -1043,34 +1020,26 @@ char *strrep(const char *s, unsigned n) { return r; } -int split_pair(const char *s, const char *sep, char **l, char **r) { - char *x, *a, *b; - +int split_pair(const char *s, const char *sep, char **ret_first, char **ret_second) { assert(s); - assert(sep); - assert(l); - assert(r); + assert(!isempty(sep)); + assert(ret_first); + assert(ret_second); - if (isempty(sep)) - return -EINVAL; - - x = strstr(s, sep); + const char *x = strstr(s, sep); if (!x) return -EINVAL; - a = strndup(s, x - s); + _cleanup_free_ char *a = strndup(s, x - s); if (!a) return -ENOMEM; - b = strdup(x + strlen(sep)); - if (!b) { - free(a); + _cleanup_free_ char *b = strdup(x + strlen(sep)); + if (!b) return -ENOMEM; - } - - *l = a; - *r = b; + *ret_first = TAKE_PTR(a); + *ret_second = TAKE_PTR(b); return 0; } @@ -1123,6 +1092,24 @@ int free_and_strndup(char **p, const char *s, size_t l) { return 1; } +int strdup_to_full(char **ret, const char *src) { + if (!src) { + if (ret) + *ret = NULL; + + return 0; + } else { + if (ret) { + char *t = strdup(src); + if (!t) + return -ENOMEM; + *ret = t; + } + + return 1; + } +}; + bool string_is_safe(const char *p) { if (!p) return false; @@ -1232,54 +1219,31 @@ int string_extract_line(const char *s, size_t i, char **ret) { return -ENOMEM; *ret = m; - return !isempty(q + 1); /* more coming? */ - } else { - if (p == s) - *ret = NULL; /* Just use the input string */ - else { - char *m; - - m = strdup(p); - if (!m) - return -ENOMEM; - - *ret = m; - } - - return 0; /* The end */ - } + return !isempty(q + 1); /* More coming? */ + } else + /* Tell the caller to use the input string if equal */ + return strdup_to(ret, p != s ? p : NULL); } - if (!q) { - char *m; - + if (!q) /* No more lines, return empty line */ - - m = strdup(""); - if (!m) - return -ENOMEM; - - *ret = m; - return 0; /* The end */ - } + return strdup_to(ret, ""); p = q + 1; c++; } } -int string_contains_word_strv(const char *string, const char *separators, char **words, const char **ret_word) { - /* In the default mode with no separators specified, we split on whitespace and - * don't coalesce separators. */ +int string_contains_word_strv(const char *string, const char *separators, char * const *words, const char **ret_word) { + /* In the default mode with no separators specified, we split on whitespace and coalesce separators. */ const ExtractFlags flags = separators ? EXTRACT_DONT_COALESCE_SEPARATORS : 0; - const char *found = NULL; + int r; - for (const char *p = string;;) { + for (;;) { _cleanup_free_ char *w = NULL; - int r; - r = extract_first_word(&p, &w, separators, flags); + r = extract_first_word(&string, &w, separators, flags); if (r < 0) return r; if (r == 0) @@ -1311,7 +1275,7 @@ bool streq_skip_trailing_chars(const char *s1, const char *s2, const char *ok) { return in_charset(s1, ok) && in_charset(s2, ok); } -char *string_replace_char(char *str, char old_char, char new_char) { +char* string_replace_char(char *str, char old_char, char new_char) { assert(str); assert(old_char != '\0'); assert(new_char != '\0'); @@ -1381,14 +1345,14 @@ size_t strspn_from_end(const char *str, const char *accept) { return n; } -char *strdupspn(const char *a, const char *accept) { +char* strdupspn(const char *a, const char *accept) { if (isempty(a) || isempty(accept)) return strdup(""); return strndup(a, strspn(a, accept)); } -char *strdupcspn(const char *a, const char *reject) { +char* strdupcspn(const char *a, const char *reject) { if (isempty(a)) return strdup(""); if (isempty(reject)) @@ -1397,7 +1361,7 @@ char *strdupcspn(const char *a, const char *reject) { return strndup(a, strcspn(a, reject)); } -char *find_line_startswith(const char *haystack, const char *needle) { +char* find_line_startswith(const char *haystack, const char *needle) { char *p; assert(haystack); @@ -1508,7 +1472,7 @@ ssize_t strlevenshtein(const char *x, const char *y) { return t1[yl]; } -char *strrstr(const char *haystack, const char *needle) { +char* strrstr(const char *haystack, const char *needle) { /* Like strstr() but returns the last rather than the first occurrence of "needle" in "haystack". */ if (!haystack || !needle) diff --git a/src/libnm-systemd-shared/src/basic/string-util.h b/src/libnm-systemd-shared/src/basic/string-util.h index e162765aa7..1bcb1c40e3 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.h +++ b/src/libnm-systemd-shared/src/basic/string-util.h @@ -8,6 +8,7 @@ #include "alloc-util.h" #include "macro.h" #include "string-util-fundamental.h" +#include "utf8.h" /* What is interpreted as whitespace? */ #define WHITESPACE " \t\n\r" @@ -32,7 +33,7 @@ static inline char* strstr_ptr(const char *haystack, const char *needle) { return strstr(haystack, needle); } -static inline char *strstrafter(const char *haystack, const char *needle) { +static inline char* strstrafter(const char *haystack, const char *needle) { char *p; /* Returns NULL if not found, or pointer to first character after needle if found */ @@ -48,7 +49,7 @@ static inline const char* strnull(const char *s) { return s ?: "(null)"; } -static inline const char *strna(const char *s) { +static inline const char* strna(const char *s) { return s ?: "n/a"; } @@ -80,11 +81,11 @@ static inline const char* enabled_disabled(bool b) { (typeof(p)) (isempty(_p) ? NULL : _p); \ }) -static inline const char *empty_to_na(const char *p) { +static inline const char* empty_to_na(const char *p) { return isempty(p) ? "n/a" : p; } -static inline const char *empty_to_dash(const char *str) { +static inline const char* empty_to_dash(const char *str) { return isempty(str) ? "-" : str; } @@ -94,7 +95,7 @@ static inline bool empty_or_dash(const char *str) { (str[0] == '-' && str[1] == 0); } -static inline const char *empty_or_dash_to_null(const char *p) { +static inline const char* empty_or_dash_to_null(const char *p) { return empty_or_dash(p) ? NULL : p; } #define empty_or_dash_to_null(p) \ @@ -103,12 +104,11 @@ static inline const char *empty_or_dash_to_null(const char *p) { (typeof(p)) (empty_or_dash(_p) ? NULL : _p); \ }) -char *first_word(const char *s, const char *word) _pure_; +char* first_word(const char *s, const char *word) _pure_; -char *strnappend(const char *s, const char *suffix, size_t length); +char* strnappend(const char *s, const char *suffix, size_t length); -char *strjoin_real(const char *x, ...) _sentinel_; -#define strjoin(a, ...) strjoin_real((a), __VA_ARGS__, NULL) +#define strjoin(a, ...) strextend_with_separator_internal(NULL, NULL, a, __VA_ARGS__, NULL) #define strjoina(a, ...) \ ({ \ @@ -125,15 +125,15 @@ char *strjoin_real(const char *x, ...) _sentinel_; _d_; \ }) -char *strstrip(char *s); -char *delete_chars(char *s, const char *bad); -char *delete_trailing_chars(char *s, const char *bad); -char *truncate_nl_full(char *s, size_t *ret_len); -static inline char *truncate_nl(char *s) { +char* strstrip(char *s); +char* delete_chars(char *s, const char *bad); +char* delete_trailing_chars(char *s, const char *bad); +char* truncate_nl_full(char *s, size_t *ret_len); +static inline char* truncate_nl(char *s) { return truncate_nl_full(s, NULL); } -static inline char *skip_leading_chars(const char *s, const char *bad) { +static inline char* skip_leading_chars(const char *s, const char *bad) { if (!s) return NULL; @@ -144,18 +144,18 @@ static inline char *skip_leading_chars(const char *s, const char *bad) { } char ascii_tolower(char x); -char *ascii_strlower(char *s); -char *ascii_strlower_n(char *s, size_t n); +char* ascii_strlower(char *s); +char* ascii_strlower_n(char *s, size_t n); char ascii_toupper(char x); -char *ascii_strupper(char *s); +char* ascii_strupper(char *s); int ascii_strcasecmp_n(const char *a, const char *b, size_t n); int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m); bool chars_intersect(const char *a, const char *b) _pure_; -static inline bool _pure_ in_charset(const char *s, const char* charset) { +static inline bool _pure_ in_charset(const char *s, const char *charset) { assert(s); assert(charset); return s[strspn(s, charset)] == '\0'; @@ -171,12 +171,12 @@ static inline bool char_is_cc(char p) { } bool string_has_cc(const char *p, const char *ok) _pure_; -char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent); -static inline char *ellipsize(const char *s, size_t length, unsigned percent) { +char* ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent); +static inline char* ellipsize(const char *s, size_t length, unsigned percent) { return ellipsize_mem(s, strlen(s), length, percent); } -char *cellescape(char *buf, size_t len, const char *s); +char* cellescape(char *buf, size_t len, const char *s); /* This limit is arbitrary, enough to give some idea what the string contains */ #define CELLESCAPE_DEFAULT_LENGTH 64 @@ -185,33 +185,35 @@ char* strshorten(char *s, size_t l); int strgrowpad0(char **s, size_t l); -char *strreplace(const char *text, const char *old_string, const char *new_string); +char* strreplace(const char *text, const char *old_string, const char *new_string); -char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]); +char* strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]); -char *strextend_with_separator_internal(char **x, const char *separator, ...) _sentinel_; +char* strextend_with_separator_internal(char **x, const char *separator, ...) _sentinel_; #define strextend_with_separator(x, separator, ...) strextend_with_separator_internal(x, separator, __VA_ARGS__, NULL) #define strextend(x, ...) strextend_with_separator_internal(x, NULL, __VA_ARGS__, NULL) -char *strextendn(char **x, const char *s, size_t l); +char* strextendn(char **x, const char *s, size_t l); int strextendf_with_separator(char **x, const char *separator, const char *format, ...) _printf_(3,4); #define strextendf(x, ...) strextendf_with_separator(x, NULL, __VA_ARGS__) -char *strrep(const char *s, unsigned n); +char* strrep(const char *s, unsigned n); -#define strrepa(s, n) \ - ({ \ - char *_d_, *_p_; \ - size_t _len_ = strlen(s) * n; \ - _p_ = _d_ = newa(char, _len_ + 1); \ - for (unsigned _i_ = 0; _i_ < n; _i_++) \ - _p_ = stpcpy(_p_, s); \ - *_p_ = 0; \ - _d_; \ +#define strrepa(s, n) \ + ({ \ + const char *_sss_ = (s); \ + size_t _nnn_ = (n), _len_ = strlen(_sss_); \ + assert_se(MUL_ASSIGN_SAFE(&_len_, _nnn_)); \ + char *_d_, *_p_; \ + _p_ = _d_ = newa(char, _len_ + 1); \ + for (size_t _i_ = 0; _i_ < _nnn_; _i_++) \ + _p_ = stpcpy(_p_, _sss_); \ + *_p_ = 0; \ + _d_; \ }) -int split_pair(const char *s, const char *sep, char **l, char **r); +int split_pair(const char *s, const char *sep, char **ret_first, char **ret_second); int free_and_strdup(char **p, const char *s); static inline int free_and_strdup_warn(char **p, const char *s) { @@ -224,7 +226,16 @@ static inline int free_and_strdup_warn(char **p, const char *s) { } int free_and_strndup(char **p, const char *s, size_t l); +int strdup_to_full(char **ret, const char *src); +static inline int strdup_to(char **ret, const char *src) { + int r = strdup_to_full(ASSERT_PTR(ret), src); + return r < 0 ? r : 0; /* Suppress return value of 1. */ +} + bool string_is_safe(const char *p) _pure_; +static inline bool string_is_safe_ascii(const char *p) { + return ascii_is_valid(p) && string_is_safe(p); +} DISABLE_WARNING_STRINGOP_TRUNCATION; static inline void strncpy_exact(char *buf, const char *src, size_t buf_len) { @@ -235,7 +246,7 @@ REENABLE_WARNING; /* Like startswith_no_case(), but operates on arbitrary memory blocks. * It works only for ASCII strings. */ -static inline void *memory_startswith_no_case(const void *p, size_t sz, const char *token) { +static inline void* memory_startswith_no_case(const void *p, size_t sz, const char *token) { assert(token); size_t n = strlen(token); @@ -265,14 +276,14 @@ char* string_erase(char *x); int string_truncate_lines(const char *s, size_t n_lines, char **ret); int string_extract_line(const char *s, size_t i, char **ret); -int string_contains_word_strv(const char *string, const char *separators, char **words, const char **ret_word); +int string_contains_word_strv(const char *string, const char *separators, char * const *words, const char **ret_word); static inline int string_contains_word(const char *string, const char *separators, const char *word) { return string_contains_word_strv(string, separators, STRV_MAKE(word), NULL); } bool streq_skip_trailing_chars(const char *s1, const char *s2, const char *ok); -char *string_replace_char(char *str, char old_char, char new_char); +char* string_replace_char(char *str, char old_char, char new_char); typedef enum MakeCStringMode { MAKE_CSTRING_REFUSE_TRAILING_NUL, @@ -286,10 +297,10 @@ int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret); size_t strspn_from_end(const char *str, const char *accept); -char *strdupspn(const char *a, const char *accept); -char *strdupcspn(const char *a, const char *reject); +char* strdupspn(const char *a, const char *accept); +char* strdupcspn(const char *a, const char *reject); -char *find_line_startswith(const char *haystack, const char *needle); +char* find_line_startswith(const char *haystack, const char *needle); bool version_is_valid(const char *s); @@ -297,25 +308,4 @@ bool version_is_valid_versionspec(const char *s); ssize_t strlevenshtein(const char *x, const char *y); -static inline int strdup_or_null(const char *s, char **ret) { - char *c; - - assert(ret); - - /* This is a lot like strdup(), but is happy with NULL strings, and does not treat that as error, but - * copies the NULL value. */ - - if (!s) { - *ret = NULL; - return 0; - } - - c = strdup(s); - if (!c) - return -ENOMEM; - - *ret = c; - return 1; -} - -char *strrstr(const char *haystack, const char *needle); +char* strrstr(const char *haystack, const char *needle); diff --git a/src/libnm-systemd-shared/src/basic/strv.c b/src/libnm-systemd-shared/src/basic/strv.c index 72cbbfe2f4..c9c4551cdc 100644 --- a/src/libnm-systemd-shared/src/basic/strv.c +++ b/src/libnm-systemd-shared/src/basic/strv.c @@ -11,11 +11,13 @@ #include "escape.h" #include "extract-word.h" #include "fileio.h" +#include "gunicode.h" #include "memory-util.h" #include "nulstr-util.h" #include "sort-util.h" #include "string-util.h" #include "strv.h" +#include "utf8.h" char* strv_find(char * const *l, const char *name) { assert(name); @@ -64,6 +66,68 @@ char* strv_find_startswith(char * const *l, const char *name) { return NULL; } +static char* strv_find_closest_prefix(char * const *l, const char *name) { + size_t best_distance = SIZE_MAX; + char *best = NULL; + + assert(name); + + STRV_FOREACH(s, l) { + char *e = startswith(*s, name); + if (!e) + continue; + + size_t n = strlen(e); + if (n < best_distance) { + best_distance = n; + best = *s; + } + } + + return best; +} + +static char* strv_find_closest_by_levenshtein(char * const *l, const char *name) { + ssize_t best_distance = SSIZE_MAX; + char *best = NULL; + + assert(name); + + STRV_FOREACH(i, l) { + ssize_t distance; + + distance = strlevenshtein(*i, name); + if (distance < 0) { + log_debug_errno(distance, "Failed to determine Levenshtein distance between %s and %s: %m", *i, name); + return NULL; + } + + if (distance > 5) /* If the distance is just too far off, don't make a bad suggestion */ + continue; + + if (distance < best_distance) { + best_distance = distance; + best = *i; + } + } + + return best; +} + +char* strv_find_closest(char * const *l, const char *name) { + assert(name); + + /* Be more helpful to the user, and give a hint what the user might have wanted to type. We search + * with two mechanisms: a simple prefix match and – if that didn't yield results –, a Levenshtein + * word distance based match. */ + + char *found = strv_find_closest_prefix(l, name); + if (found) + return found; + + return strv_find_closest_by_levenshtein(l, name); +} + char* strv_find_first_field(char * const *needles, char * const *haystack) { STRV_FOREACH(k, needles) { char *value = strv_env_pairs_get((char **)haystack, *k); @@ -200,20 +264,18 @@ char** strv_new_internal(const char *x, ...) { int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates) { size_t p, q, i = 0; - char **t; assert(a); - if (strv_isempty(b)) + q = strv_length(b); + if (q == 0) return 0; p = strv_length(*a); - q = strv_length(b); - if (p >= SIZE_MAX - q) return -ENOMEM; - t = reallocarray(*a, GREEDY_ALLOC_ROUND_UP(p + q + 1), sizeof(char *)); + char **t = reallocarray(*a, GREEDY_ALLOC_ROUND_UP(p + q + 1), sizeof(char *)); if (!t) return -ENOMEM; @@ -242,21 +304,77 @@ rollback: return -ENOMEM; } -int strv_extend_strv_concat(char ***a, char * const *b, const char *suffix) { +int strv_extend_strv_consume(char ***a, char **b, bool filter_duplicates) { + _cleanup_strv_free_ char **b_consume = b; + size_t p, q, i; + + assert(a); + + q = strv_length(b); + if (q == 0) + return 0; + + p = strv_length(*a); + if (p == 0) { + strv_free_and_replace(*a, b_consume); + + if (filter_duplicates) + strv_uniq(*a); + + return strv_length(*a); + } + + if (p >= SIZE_MAX - q) + return -ENOMEM; + + char **t = reallocarray(*a, GREEDY_ALLOC_ROUND_UP(p + q + 1), sizeof(char *)); + if (!t) + return -ENOMEM; + + t[p] = NULL; + *a = t; + + if (!filter_duplicates) { + *mempcpy_typesafe(t + p, b, q) = NULL; + i = q; + } else { + i = 0; + + STRV_FOREACH(s, b) { + if (strv_contains(t, *s)) { + free(*s); + continue; + } + + t[p+i] = *s; + + i++; + t[p+i] = NULL; + } + } + + assert(i <= q); + + b_consume = mfree(b_consume); + + return (int) i; +} + +int strv_extend_strv_biconcat(char ***a, const char *prefix, const char* const *b, const char *suffix) { int r; + assert(a); + STRV_FOREACH(s, b) { char *v; - v = strjoin(*s, suffix); + v = strjoin(strempty(prefix), *s, suffix); if (!v) return -ENOMEM; - r = strv_push(a, v); - if (r < 0) { - free(v); + r = strv_consume(a, v); + if (r < 0) return r; - } } return 0; @@ -322,7 +440,7 @@ int strv_split_full(char ***t, const char *s, const char *separators, ExtractFla } int strv_split_and_extend_full(char ***t, const char *s, const char *separators, bool filter_duplicates, ExtractFlags flags) { - _cleanup_strv_free_ char **l = NULL; + char **l; int r; assert(t); @@ -332,7 +450,7 @@ int strv_split_and_extend_full(char ***t, const char *s, const char *separators, if (r < 0) return r; - r = strv_extend_strv(t, l, filter_duplicates); + r = strv_extend_strv_consume(t, l, filter_duplicates); if (r < 0) return r; @@ -358,7 +476,7 @@ int strv_split_colon_pairs(char ***t, const char *s) { const char *p = tuple; r = extract_many_words(&p, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, - &first, &second, NULL); + &first, &second); if (r < 0) return r; if (r == 0) @@ -706,6 +824,21 @@ char** strv_sort(char **l) { return l; } +char** strv_sort_uniq(char **l) { + if (strv_isempty(l)) + return l; + + char **tail = strv_sort(l), *prev = NULL; + STRV_FOREACH(i, l) + if (streq_ptr(*i, prev)) + free(*i); + else + *(tail++) = prev = *i; + + *tail = NULL; + return l; +} + int strv_compare(char * const *a, char * const *b) { int r; @@ -728,6 +861,26 @@ int strv_compare(char * const *a, char * const *b) { return 0; } +bool strv_equal_ignore_order(char **a, char **b) { + + /* Just like strv_equal(), but doesn't care about the order of elements or about redundant entries + * (i.e. it's even ok if the number of entries in the array differ, as long as the difference just + * consists of repititions) */ + + if (a == b) + return true; + + STRV_FOREACH(i, a) + if (!strv_contains(b, *i)) + return false; + + STRV_FOREACH(i, b) + if (!strv_contains(a, *i)) + return false; + + return true; +} + void strv_print_full(char * const *l, const char *prefix) { STRV_FOREACH(s, l) printf("%s%s\n", strempty(prefix), *s); @@ -910,10 +1063,16 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) { return 0; } +DEFINE_PRIVATE_HASH_OPS_FULL(string_strv_hash_ops, char, string_hash_func, string_compare_func, free, char*, strv_free); + static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const char *value) { char **l; int r; + assert(h); + assert(key); + assert(value); + l = hashmap_get(h, key); if (l) { /* A list for this key already exists, let's append to it if it is not listed yet */ @@ -941,6 +1100,7 @@ static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const c r = hashmap_put(h, t, l2); if (r < 0) return r; + TAKE_PTR(t); TAKE_PTR(l2); } @@ -951,6 +1111,10 @@ static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const c int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS) { int r; + assert(h); + assert(key); + assert(value); + r = _hashmap_ensure_allocated(h, &string_strv_hash_ops HASHMAP_DEBUG_PASS_ARGS); if (r < 0) return r; @@ -961,6 +1125,10 @@ int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value HA int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS) { int r; + assert(h); + assert(key); + assert(value); + r = _ordered_hashmap_ensure_allocated(h, &string_strv_hash_ops HASHMAP_DEBUG_PASS_ARGS); if (r < 0) return r; @@ -968,4 +1136,90 @@ int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const return string_strv_hashmap_put_internal(PLAIN_HASHMAP(*h), key, value); } -DEFINE_HASH_OPS_FULL(string_strv_hash_ops, char, string_hash_func, string_compare_func, free, char*, strv_free); +int strv_rebreak_lines(char **l, size_t width, char ***ret) { + _cleanup_strv_free_ char **broken = NULL; + int r; + + assert(ret); + + /* Implements a simple UTF-8 line breaking algorithm + * + * Goes through all entries in *l, and line-breaks each line that is longer than the specified + * character width. Breaks at the end of words/beginning of whitespace. Lines that do not contain whitespace are not + * broken. Retains whitespace at beginning of lines, removes it at end of lines. */ + + if (width == SIZE_MAX) { /* NOP? */ + broken = strv_copy(l); + if (!broken) + return -ENOMEM; + + *ret = TAKE_PTR(broken); + return 0; + } + + STRV_FOREACH(i, l) { + const char *start = *i, *whitespace_begin = NULL, *whitespace_end = NULL; + bool in_prefix = true; /* still in the whitespace in the beginning of the line? */ + size_t w = 0; + + for (const char *p = start; *p != 0; p = utf8_next_char(p)) { + if (strchr(NEWLINE, *p)) { + in_prefix = true; + whitespace_begin = whitespace_end = NULL; + w = 0; + } else if (strchr(WHITESPACE, *p)) { + if (!in_prefix && (!whitespace_begin || whitespace_end)) { + whitespace_begin = p; + whitespace_end = NULL; + } + } else { + if (whitespace_begin && !whitespace_end) + whitespace_end = p; + + in_prefix = false; + } + + int cw = utf8_char_console_width(p); + if (cw < 0) { + log_debug_errno(cw, "Comment to line break contains invalid UTF-8, ignoring."); + cw = 1; + } + + w += cw; + + if (w > width && whitespace_begin && whitespace_end) { + _cleanup_free_ char *truncated = NULL; + + truncated = strndup(start, whitespace_begin - start); + if (!truncated) + return -ENOMEM; + + r = strv_consume(&broken, TAKE_PTR(truncated)); + if (r < 0) + return r; + + p = start = whitespace_end; + whitespace_begin = whitespace_end = NULL; + w = cw; + } + } + + /* Process rest of the line */ + assert(start); + if (in_prefix) /* Never seen anything non-whitespace? Generate empty line! */ + r = strv_extend(&broken, ""); + else if (whitespace_begin && !whitespace_end) { /* Ends in whitespace? Chop it off! */ + _cleanup_free_ char *truncated = strndup(start, whitespace_begin - start); + if (!truncated) + return -ENOMEM; + + r = strv_consume(&broken, TAKE_PTR(truncated)); + } else /* Otherwise use line as is */ + r = strv_extend(&broken, start); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(broken); + return 0; +} diff --git a/src/libnm-systemd-shared/src/basic/strv.h b/src/libnm-systemd-shared/src/basic/strv.h index 91337b9287..86ba06f835 100644 --- a/src/libnm-systemd-shared/src/basic/strv.h +++ b/src/libnm-systemd-shared/src/basic/strv.h @@ -17,6 +17,7 @@ char* strv_find(char * const *l, const char *name) _pure_; char* strv_find_case(char * const *l, const char *name) _pure_; char* strv_find_prefix(char * const *l, const char *name) _pure_; char* strv_find_startswith(char * const *l, const char *name) _pure_; +char* strv_find_closest(char * const *l, const char *name) _pure_; /* Given two vectors, the first a list of keys and the second a list of key-value pairs, returns the value * of the first key from the first vector that is found in the second vector. */ char* strv_find_first_field(char * const *needles, char * const *haystack) _pure_; @@ -43,7 +44,13 @@ int strv_copy_unless_empty(char * const *l, char ***ret); size_t strv_length(char * const *l) _pure_; int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates); -int strv_extend_strv_concat(char ***a, char * const *b, const char *suffix); +int strv_extend_strv_consume(char ***a, char **b, bool filter_duplicates); + +int strv_extend_strv_biconcat(char ***a, const char *prefix, const char* const *b, const char *suffix); +static inline int strv_extend_strv_concat(char ***a, const char* const *b, const char *suffix) { + return strv_extend_strv_biconcat(a, NULL, b, suffix); +} + int strv_prepend(char ***l, const char *value); /* _with_size() are lower-level functions where the size can be provided externally, @@ -89,6 +96,8 @@ static inline bool strv_equal(char * const *a, char * const *b) { return strv_compare(a, b) == 0; } +bool strv_equal_ignore_order(char **a, char **b); + char** strv_new_internal(const char *x, ...) _sentinel_; char** strv_new_ap(const char *x, va_list ap); #define strv_new(...) strv_new_internal(__VA_ARGS__, NULL) @@ -150,7 +159,7 @@ bool strv_overlap(char * const *a, char * const *b) _pure_; _STRV_FOREACH_BACKWARDS(s, l, UNIQ_T(h, UNIQ), UNIQ_T(i, UNIQ)) #define _STRV_FOREACH_PAIR(x, y, l, i) \ - for (typeof(*l) *x, *y, *i = (l); \ + for (typeof(*(l)) *x, *y, *i = (l); \ i && *(x = i) && *(y = i + 1); \ i += 2) @@ -158,6 +167,7 @@ bool strv_overlap(char * const *a, char * const *b) _pure_; _STRV_FOREACH_PAIR(x, y, l, UNIQ_T(i, UNIQ)) char** strv_sort(char **l); +char** strv_sort_uniq(char **l); void strv_print_full(char * const *l, const char *prefix); static inline void strv_print(char * const *l) { strv_print_full(l, NULL); @@ -231,7 +241,6 @@ bool strv_fnmatch_full(char* const* patterns, const char *s, int flags, size_t * static inline bool strv_fnmatch(char* const* patterns, const char *s) { return strv_fnmatch_full(patterns, s, 0, NULL); } - static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, int flags) { assert(s); return strv_isempty(patterns) || @@ -249,8 +258,9 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space); #define strv_free_and_replace(a, b) \ free_and_replace_full(a, b, strv_free) -extern const struct hash_ops string_strv_hash_ops; int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS); int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS); #define string_strv_hashmap_put(h, k, v) _string_strv_hashmap_put(h, k, v HASHMAP_DEBUG_SRC_ARGS) #define string_strv_ordered_hashmap_put(h, k, v) _string_strv_ordered_hashmap_put(h, k, v HASHMAP_DEBUG_SRC_ARGS) + +int strv_rebreak_lines(char **l, size_t width, char ***ret); diff --git a/src/libnm-systemd-shared/src/basic/time-util.c b/src/libnm-systemd-shared/src/basic/time-util.c index 02123dc591..aa51334100 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.c +++ b/src/libnm-systemd-shared/src/basic/time-util.c @@ -83,7 +83,7 @@ triple_timestamp* triple_timestamp_now(triple_timestamp *ts) { return ts; } -static usec_t map_clock_usec_internal(usec_t from, usec_t from_base, usec_t to_base) { +usec_t map_clock_usec_raw(usec_t from, usec_t from_base, usec_t to_base) { /* Maps the time 'from' between two clocks, based on a common reference point where the first clock * is at 'from_base' and the second clock at 'to_base'. Basically calculates: @@ -121,7 +121,7 @@ usec_t map_clock_usec(usec_t from, clockid_t from_clock, clockid_t to_clock) { if (from == USEC_INFINITY) return from; - return map_clock_usec_internal(from, now(from_clock), now(to_clock)); + return map_clock_usec_raw(from, now(from_clock), now(to_clock)); } dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { @@ -150,8 +150,8 @@ triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) nowr = now(CLOCK_REALTIME); ts->realtime = u; - ts->monotonic = map_clock_usec_internal(u, nowr, now(CLOCK_MONOTONIC)); - ts->boottime = map_clock_usec_internal(u, nowr, now(CLOCK_BOOTTIME)); + ts->monotonic = map_clock_usec_raw(u, nowr, now(CLOCK_MONOTONIC)); + ts->boottime = map_clock_usec_raw(u, nowr, now(CLOCK_BOOTTIME)); return ts; } @@ -169,8 +169,8 @@ triple_timestamp* triple_timestamp_from_boottime(triple_timestamp *ts, usec_t u) nowb = now(CLOCK_BOOTTIME); ts->boottime = u; - ts->monotonic = map_clock_usec_internal(u, nowb, now(CLOCK_MONOTONIC)); - ts->realtime = map_clock_usec_internal(u, nowb, now(CLOCK_REALTIME)); + ts->monotonic = map_clock_usec_raw(u, nowb, now(CLOCK_MONOTONIC)); + ts->realtime = map_clock_usec_raw(u, nowb, now(CLOCK_REALTIME)); return ts; } @@ -199,8 +199,8 @@ dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) { } nowm = now(CLOCK_BOOTTIME); - ts->monotonic = map_clock_usec_internal(u, nowm, now(CLOCK_MONOTONIC)); - ts->realtime = map_clock_usec_internal(u, nowm, now(CLOCK_REALTIME)); + ts->monotonic = map_clock_usec_raw(u, nowm, now(CLOCK_MONOTONIC)); + ts->realtime = map_clock_usec_raw(u, nowm, now(CLOCK_REALTIME)); return ts; } @@ -312,7 +312,7 @@ struct timeval *timeval_store(struct timeval *tv, usec_t u) { return tv; } -char *format_timestamp_style( +char* format_timestamp_style( char *buf, size_t l, usec_t t, @@ -332,7 +332,6 @@ char *format_timestamp_style( struct tm tm; bool utc, us; - time_t sec; size_t n; assert(buf); @@ -375,9 +374,7 @@ char *format_timestamp_style( return strcpy(buf, xxx[style]); } - sec = (time_t) (t / USEC_PER_SEC); /* Round down */ - - if (!localtime_or_gmtime_r(&sec, &tm, utc)) + if (localtime_or_gmtime_usec(t, utc, &tm) < 0) return NULL; /* Start with the week day */ @@ -559,7 +556,7 @@ char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { /* The result of this function can be parsed with parse_sec */ - for (size_t i = 0; i < ELEMENTSOF(table); i++) { + FOREACH_ELEMENT(i, table) { int k = 0; size_t n; bool done = false; @@ -571,20 +568,20 @@ char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { if (t < accuracy && something) break; - if (t < table[i].usec) + if (t < i->usec) continue; if (l <= 1) break; - a = t / table[i].usec; - b = t % table[i].usec; + a = t / i->usec; + b = t % i->usec; /* Let's see if we should shows this in dot notation */ if (t < USEC_PER_MINUTE && b > 0) { signed char j = 0; - for (usec_t cc = table[i].usec; cc > 1; cc /= 10) + for (usec_t cc = i->usec; cc > 1; cc /= 10) j++; for (usec_t cc = accuracy; cc > 1; cc /= 10) { @@ -599,7 +596,7 @@ char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { a, j, b, - table[i].suffix); + i->suffix); t = 0; done = true; @@ -612,7 +609,7 @@ char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { "%s"USEC_FMT"%s", p > buf ? " " : "", a, - table[i].suffix); + i->suffix); t = b; } @@ -665,7 +662,6 @@ static int parse_timestamp_impl( unsigned fractional = 0; const char *k; struct tm tm, copy; - time_t sec; /* Allowed syntaxes: * @@ -778,10 +774,9 @@ static int parse_timestamp_impl( } } - sec = (time_t) (usec / USEC_PER_SEC); - - if (!localtime_or_gmtime_r(&sec, &tm, utc)) - return -EINVAL; + r = localtime_or_gmtime_usec(usec, utc, &tm); + if (r < 0) + return r; tm.tm_isdst = isdst; @@ -800,12 +795,12 @@ static int parse_timestamp_impl( goto from_tm; } - for (size_t i = 0; i < ELEMENTSOF(day_nr); i++) { - k = startswith_no_case(t, day_nr[i].name); + FOREACH_ELEMENT(day, day_nr) { + k = startswith_no_case(t, day->name); if (!k || *k != ' ') continue; - weekday = day_nr[i].nr; + weekday = day->nr; t = k + 1; break; } @@ -939,11 +934,11 @@ from_tm: } else minus = gmtoff * USEC_PER_SEC; - sec = mktime_or_timegm(&tm, utc); - if (sec < 0) - return -EINVAL; + r = mktime_or_timegm_usec(&tm, utc, &usec); + if (r < 0) + return r; - usec = usec_add(sec * USEC_PER_SEC, fractional); + usec = usec_add(usec, fractional); finish: usec = usec_add(usec, plus); @@ -999,8 +994,12 @@ int parse_timestamp(const char *t, usec_t *ret) { assert(t); t_len = strlen(t); - if (t_len > 2 && t[t_len - 1] == 'Z' && t[t_len - 2] != ' ') /* RFC3339-style welded UTC: "1985-04-12T23:20:50.52Z" */ - return parse_timestamp_impl(t, t_len - 1, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret); + if (t_len > 2 && t[t_len - 1] == 'Z') { + /* Try to parse as RFC3339-style welded UTC: "1985-04-12T23:20:50.52Z" */ + r = parse_timestamp_impl(t, t_len - 1, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret); + if (r >= 0) + return r; + } if (t_len > 7 && IN_SET(t[t_len - 6], '+', '-') && t[t_len - 7] != ' ') { /* RFC3339-style welded offset: "1990-12-31T15:59:60-08:00" */ k = strptime(&t[t_len - 6], "%z", &tm); @@ -1044,6 +1043,14 @@ int parse_timestamp(const char *t, usec_t *ret) { if (shared == MAP_FAILED) return negative_errno(); + /* The input string may be in argv. Let's copy it. */ + _cleanup_free_ char *t_copy = strdup(t); + if (!t_copy) + return -ENOMEM; + + t = t_copy; + assert_se(tz = endswith(t_copy, tz)); + r = safe_fork("(sd-timestamp)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, NULL); if (r < 0) { (void) munmap(shared, sizeof *shared); @@ -1115,12 +1122,12 @@ static const char* extract_multiplier(const char *p, usec_t *ret) { assert(p); assert(ret); - for (size_t i = 0; i < ELEMENTSOF(table); i++) { + FOREACH_ELEMENT(i, table) { char *e; - e = startswith(p, table[i].suffix); + e = startswith(p, i->suffix); if (e) { - *ret = table[i].usec; + *ret = i->usec; return e; } } @@ -1130,19 +1137,14 @@ static const char* extract_multiplier(const char *p, usec_t *ret) { int parse_time(const char *t, usec_t *ret, usec_t default_unit) { const char *p, *s; - usec_t usec = 0; - bool something = false; assert(t); assert(default_unit > 0); - p = t; - - p += strspn(p, WHITESPACE); + p = skip_leading_chars(t, /* bad = */ NULL); s = startswith(p, "infinity"); if (s) { - s += strspn(s, WHITESPACE); - if (*s != 0) + if (!in_charset(s, WHITESPACE)) return -EINVAL; if (ret) @@ -1150,13 +1152,14 @@ int parse_time(const char *t, usec_t *ret, usec_t default_unit) { return 0; } - for (;;) { + usec_t usec = 0; + + for (bool something = false;;) { usec_t multiplier = default_unit, k; long long l; char *e; - p += strspn(p, WHITESPACE); - + p = skip_leading_chars(p, /* bad = */ NULL); if (*p == 0) { if (!something) return -EINVAL; @@ -1293,17 +1296,16 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) { { "ns", 1ULL }, { "", 1ULL }, /* default is nsec */ }; - size_t i; assert(p); assert(ret); - for (i = 0; i < ELEMENTSOF(table); i++) { + FOREACH_ELEMENT(i, table) { char *e; - e = startswith(p, table[i].suffix); + e = startswith(p, i->suffix); if (e) { - *ret = table[i].nsec; + *ret = i->nsec; return e; } } @@ -1429,7 +1431,7 @@ static int get_timezones_from_zone1970_tab(char ***ret) { /* Line format is: * 'country codes' 'coordinates' 'timezone' 'comments' */ - r = extract_many_words(&p, NULL, 0, &cc, &co, &tz, NULL); + r = extract_many_words(&p, NULL, 0, &cc, &co, &tz); if (r < 0) continue; @@ -1474,7 +1476,7 @@ static int get_timezones_from_tzdata_zi(char ***ret) { * Link line format is: * 'Link' 'target' 'alias' * See 'man zic' for more detail. */ - r = extract_many_words(&p, NULL, 0, &type, &f1, &f2, NULL); + r = extract_many_words(&p, NULL, 0, &type, &f1, &f2); if (r < 0) continue; @@ -1519,8 +1521,7 @@ int get_timezones(char ***ret) { if (r < 0) return r; - strv_sort(zones); - strv_uniq(zones); + strv_sort_uniq(zones); *ret = TAKE_PTR(zones); return 0; @@ -1573,7 +1574,7 @@ int verify_timezone(const char *name, int log_level) { r = fd_verify_regular(fd); if (r < 0) - return log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t); + return log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t); r = loop_read_exact(fd, buf, 4, false); if (r < 0) @@ -1606,51 +1607,74 @@ bool clock_supported(clockid_t clock) { int get_timezone(char **ret) { _cleanup_free_ char *t = NULL; - const char *e; - char *z; int r; assert(ret); r = readlink_malloc("/etc/localtime", &t); - if (r == -ENOENT) { + if (r == -ENOENT) /* If the symlink does not exist, assume "UTC", like glibc does */ - z = strdup("UTC"); - if (!z) - return -ENOMEM; - - *ret = z; - return 0; - } + return strdup_to(ret, "UTC"); if (r < 0) - return r; /* returns EINVAL if not a symlink */ + return r; /* Return EINVAL if not a symlink */ - e = PATH_STARTSWITH_SET(t, "/usr/share/zoneinfo/", "../usr/share/zoneinfo/"); + const char *e = PATH_STARTSWITH_SET(t, "/usr/share/zoneinfo/", "../usr/share/zoneinfo/"); if (!e) return -EINVAL; - if (!timezone_is_valid(e, LOG_DEBUG)) return -EINVAL; - z = strdup(e); - if (!z) - return -ENOMEM; + return strdup_to(ret, e); +} - *ret = z; +int mktime_or_timegm_usec( + struct tm *tm, /* input + normalized output */ + bool utc, + usec_t *ret) { + + time_t t; + + assert(tm); + + if (tm->tm_year < 69) /* early check for negative (i.e. before 1970) time_t (Note that in some timezones the epoch is in the year 1969!)*/ + return -ERANGE; + if ((usec_t) tm->tm_year > CONST_MIN(USEC_INFINITY / USEC_PER_YEAR, (usec_t) TIME_T_MAX / (365U * 24U * 60U * 60U)) - 1900) /* early check for possible overrun of usec_t or time_t */ + return -ERANGE; + + /* timegm()/mktime() is a bit weird to use, since it returns -1 in two cases: on error as well as a + * valid time indicating one second before the UNIX epoch. Let's treat both cases the same here, and + * return -ERANGE for anything negative, since usec_t is unsigned, and we can thus not express + * negative times anyway. */ + + t = utc ? timegm(tm) : mktime(tm); + if (t < 0) /* Refuse negative times and errors */ + return -ERANGE; + if ((usec_t) t >= USEC_INFINITY / USEC_PER_SEC) /* Never return USEC_INFINITY by accident (or overflow) */ + return -ERANGE; + + if (ret) + *ret = (usec_t) t * USEC_PER_SEC; return 0; } -time_t mktime_or_timegm(struct tm *tm, bool utc) { - assert(tm); +int localtime_or_gmtime_usec( + usec_t t, + bool utc, + struct tm *ret) { - return utc ? timegm(tm) : mktime(tm); -} + t /= USEC_PER_SEC; /* Round down */ + if (t > (usec_t) TIME_T_MAX) + return -ERANGE; + time_t sec = (time_t) t; -struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) { - assert(t); - assert(tm); + struct tm buf = {}; + if (!(utc ? gmtime_r(&sec, &buf) : localtime_r(&sec, &buf))) + return -EINVAL; - return utc ? gmtime_r(t, tm) : localtime_r(t, tm); + if (ret) + *ret = buf; + + return 0; } static uint32_t sysconf_clock_ticks_cached(void) { diff --git a/src/libnm-systemd-shared/src/basic/time-util.h b/src/libnm-systemd-shared/src/basic/time-util.h index 29373477f4..14d660ee7e 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.h +++ b/src/libnm-systemd-shared/src/basic/time-util.h @@ -71,12 +71,16 @@ typedef enum TimestampStyle { #define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1) -#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) {}) -#define TRIPLE_TIMESTAMP_NULL ((struct triple_timestamp) {}) +#define DUAL_TIMESTAMP_NULL ((dual_timestamp) {}) +#define DUAL_TIMESTAMP_INFINITY ((dual_timestamp) { USEC_INFINITY, USEC_INFINITY }) +#define TRIPLE_TIMESTAMP_NULL ((triple_timestamp) {}) + +#define TIMESPEC_OMIT ((const struct timespec) { .tv_nsec = UTIME_OMIT }) usec_t now(clockid_t clock); nsec_t now_nsec(clockid_t clock); +usec_t map_clock_usec_raw(usec_t from, usec_t from_base, usec_t to_base); usec_t map_clock_usec(usec_t from, clockid_t from_clock, clockid_t to_clock); dual_timestamp* dual_timestamp_now(dual_timestamp *ts); @@ -173,8 +177,8 @@ usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to); int get_timezone(char **ret); -time_t mktime_or_timegm(struct tm *tm, bool utc); -struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc); +int mktime_or_timegm_usec(struct tm *tm, bool utc, usec_t *ret); +int localtime_or_gmtime_usec(usec_t t, bool utc, struct tm *ret); uint32_t usec_to_jiffies(usec_t usec); usec_t jiffies_to_usec(uint32_t jiffies); @@ -184,11 +188,7 @@ bool in_utc_timezone(void); static inline usec_t usec_add(usec_t a, usec_t b) { /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, * and doesn't overflow. */ - - if (a > USEC_INFINITY - b) /* overflow check */ - return USEC_INFINITY; - - return a + b; + return saturate_add(a, b, USEC_INFINITY); } static inline usec_t usec_sub_unsigned(usec_t timestamp, usec_t delta) { diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.c b/src/libnm-systemd-shared/src/basic/tmpfile-util.c index e77ca94248..5addd8e8a9 100644 --- a/src/libnm-systemd-shared/src/basic/tmpfile-util.c +++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.c @@ -118,6 +118,17 @@ int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) { return 0; } +void unlink_tempfilep(char (*p)[]) { + assert(p); + + /* If the file is created with mkstemp(), it will (almost always) change the suffix. + * Treat this as a sign that the file was successfully created. We ignore both the rare case + * where the original suffix is used and unlink failures. */ + + if (!endswith(*p, ".XXXXXX")) + (void) unlink(*p); +} + static int tempfn_build(const char *p, const char *pre, const char *post, bool child, char **ret) { _cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL, *result = NULL; size_t len_pre, len_post, len_add; @@ -330,28 +341,7 @@ int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE return 0; } -static int link_fd(int fd, int newdirfd, const char *newpath) { - int r; - - assert(fd >= 0); - assert(newdirfd >= 0 || newdirfd == AT_FDCWD); - assert(newpath); - - /* Try symlinking via /proc/fd/ first. */ - r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW)); - if (r != -ENOENT) - return r; - - /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a - * more recent kernel, but does not require /proc/ mounted) */ - if (proc_mounted() != 0) - return r; - - return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH)); -} - int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, LinkTmpfileFlags flags) { - _cleanup_free_ char *tmp = NULL; int r; assert(fd >= 0); @@ -370,33 +360,14 @@ int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, Li r = RET_NERRNO(renameat(dir_fd, path, dir_fd, target)); else r = rename_noreplace(dir_fd, path, dir_fd, target); - if (r < 0) - return r; } else { - - r = link_fd(fd, dir_fd, target); - if (r != -EEXIST || !FLAGS_SET(flags, LINK_TMPFILE_REPLACE)) - return r; - - /* So the target already exists and we were asked to replace it. That sucks a bit, since the kernel's - * linkat() logic does not allow that. We work-around this by linking the file to a random name - * first, and then renaming that to the final name. This reintroduces the race O_TMPFILE kinda is - * trying to fix, but at least the vulnerability window (i.e. where the file is linked into the file - * system under a temporary name) is very short. */ - - r = tempfn_random(target, NULL, &tmp); - if (r < 0) - return r; - - if (link_fd(fd, dir_fd, tmp) < 0) - return -EEXIST; /* propagate original error */ - - r = RET_NERRNO(renameat(dir_fd, tmp, dir_fd, target)); - if (r < 0) { - (void) unlinkat(dir_fd, tmp, 0); - return r; - } + if (FLAGS_SET(flags, LINK_TMPFILE_REPLACE)) + r = linkat_replace(fd, /* oldpath= */ NULL, dir_fd, target); + else + r = link_fd(fd, dir_fd, target); } + if (r < 0) + return r; if (FLAGS_SET(flags, LINK_TMPFILE_SYNC)) { r = fsync_full(fd); diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.h b/src/libnm-systemd-shared/src/basic/tmpfile-util.h index 8c917c0680..408f80e147 100644 --- a/src/libnm-systemd-shared/src/basic/tmpfile-util.h +++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.h @@ -18,6 +18,8 @@ static inline int fopen_temporary_child(const char *path, FILE **ret_file, char int mkostemp_safe(char *pattern); int fmkostemp_safe(char *pattern, const char *mode, FILE**_f); +void unlink_tempfilep(char (*p)[]); + int tempfn_xxxxxx(const char *p, const char *extra, char **ret); int tempfn_random(const char *p, const char *extra, char **ret); int tempfn_random_child(const char *p, const char *extra, char **ret); diff --git a/src/libnm-systemd-shared/src/basic/user-util.h b/src/libnm-systemd-shared/src/basic/user-util.h index 9d07ef31d2..653f14254a 100644 --- a/src/libnm-systemd-shared/src/basic/user-util.h +++ b/src/libnm-systemd-shared/src/basic/user-util.h @@ -12,6 +12,8 @@ #include #include +#include "string-util.h" + /* Users managed by systemd-homed. See https://systemd.io/UIDS-GIDS for details how this range fits into the rest of the world */ #define HOME_UID_MIN ((uid_t) 60001) #define HOME_UID_MAX ((uid_t) 60513) @@ -36,10 +38,20 @@ static inline int parse_gid(const char *s, gid_t *ret_gid) { char* getlogname_malloc(void); char* getusername_malloc(void); +const char* default_root_shell_at(int rfd); +const char* default_root_shell(const char *root); + +bool is_nologin_shell(const char *shell); + +static inline bool shell_is_placeholder(const char *shell) { + return isempty(shell) || is_nologin_shell(shell); +} + typedef enum UserCredsFlags { - USER_CREDS_PREFER_NSS = 1 << 0, /* if set, only synthesize user records if database lacks them. Normally we bypass the userdb entirely for the records we can synthesize */ - USER_CREDS_ALLOW_MISSING = 1 << 1, /* if a numeric UID string is resolved, be OK if there's no record for it */ - USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */ + USER_CREDS_PREFER_NSS = 1 << 0, /* if set, only synthesize user records if database lacks them. Normally we bypass the userdb entirely for the records we can synthesize */ + USER_CREDS_ALLOW_MISSING = 1 << 1, /* if a numeric UID string is resolved, be OK if there's no record for it */ + USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */ + USER_CREDS_SUPPRESS_PLACEHOLDER = 1 << 3, /* suppress home and/or shell fields if value is placeholder (root/empty/nologin) */ } UserCredsFlags; int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags); @@ -52,7 +64,7 @@ int in_gid(gid_t gid); int in_group(const char *name); int merge_gid_lists(const gid_t *list1, size_t size1, const gid_t *list2, size_t size2, gid_t **result); -int getgroups_alloc(gid_t** gids); +int getgroups_alloc(gid_t **ret); int get_home_dir(char **ret); int get_shell(char **ret); @@ -108,15 +120,7 @@ bool valid_user_group_name(const char *u, ValidUserFlags flags); bool valid_gecos(const char *d); char* mangle_gecos(const char *d); bool valid_home(const char *p); - -static inline bool valid_shell(const char *p) { - /* We have the same requirements, so just piggy-back on the home check. - * - * Let's ignore /etc/shells because this is only applicable to real and - * not system users. It is also incompatible with the idea of empty /etc. - */ - return valid_home(p); -} +bool valid_shell(const char *p); int maybe_setgroups(size_t size, const gid_t *list); @@ -133,10 +137,6 @@ int fgetsgent_sane(FILE *stream, struct sgrp **sg); int putsgent_sane(const struct sgrp *sg, FILE *stream); #endif -bool is_nologin_shell(const char *shell); -const char* default_root_shell_at(int rfd); -const char* default_root_shell(const char *root); - int is_this_me(const char *username); const char* get_home_root(void); diff --git a/src/libnm-systemd-shared/src/basic/utf8.c b/src/libnm-systemd-shared/src/basic/utf8.c index 36e1e0f155..2a9da59881 100644 --- a/src/libnm-systemd-shared/src/basic/utf8.c +++ b/src/libnm-systemd-shared/src/basic/utf8.c @@ -1,26 +1,10 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-License-Identifier: LGPL-2.0-or-later */ -/* Parts of this file are based on the GLIB utf8 validation functions. The - * original license text follows. */ - -/* gutf8.c - Operations on UTF-8 strings. +/* Parts of this file are based on the GLIB utf8 validation functions. The original copyright follows. * + * gutf8.c - Operations on UTF-8 strings. * Copyright (C) 1999 Tom Tromey * Copyright (C) 2000 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include @@ -146,30 +130,30 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool allow_newlin return true; } -char *utf8_is_valid_n(const char *str, size_t len_bytes) { +char* utf8_is_valid_n(const char *str, size_t len_bytes) { /* Check if the string is composed of valid utf8 characters. If length len_bytes is given, stop after * len_bytes. Otherwise, stop at NUL. */ assert(str); - for (const char *p = str; len_bytes != SIZE_MAX ? (size_t) (p - str) < len_bytes : *p != '\0'; ) { + for (size_t i = 0; len_bytes != SIZE_MAX ? i < len_bytes : str[i] != '\0'; ) { int len; - if (_unlikely_(*p == '\0') && len_bytes != SIZE_MAX) + if (_unlikely_(str[i] == '\0')) return NULL; /* embedded NUL */ - len = utf8_encoded_valid_unichar(p, - len_bytes != SIZE_MAX ? len_bytes - (p - str) : SIZE_MAX); + len = utf8_encoded_valid_unichar(str + i, + len_bytes != SIZE_MAX ? len_bytes - i : SIZE_MAX); if (_unlikely_(len < 0)) return NULL; /* invalid character */ - p += len; + i += len; } return (char*) str; } -char *utf8_escape_invalid(const char *str) { +char* utf8_escape_invalid(const char *str) { char *p, *s; assert(str); @@ -195,7 +179,7 @@ char *utf8_escape_invalid(const char *str) { return str_realloc(p); } -static int utf8_char_console_width(const char *str) { +int utf8_char_console_width(const char *str) { char32_t c; int r; @@ -203,12 +187,15 @@ static int utf8_char_console_width(const char *str) { if (r < 0) return r; + if (c == '\t') + return 8; /* Assume a tab width of 8 */ + /* TODO: we should detect combining characters */ return unichar_iswide(c) ? 2 : 1; } -char *utf8_escape_non_printable_full(const char *str, size_t console_width, bool force_ellipsis) { +char* utf8_escape_non_printable_full(const char *str, size_t console_width, bool force_ellipsis) { char *p, *s, *prev_s; size_t n = 0; /* estimated print width */ @@ -284,27 +271,14 @@ char *utf8_escape_non_printable_full(const char *str, size_t console_width, bool return str_realloc(p); } -char *ascii_is_valid(const char *str) { - /* Check whether the string consists of valid ASCII bytes, - * i.e values between 0 and 127, inclusive. */ +char* ascii_is_valid_n(const char *str, size_t len) { + /* Check whether the string consists of valid ASCII bytes, i.e values between 1 and 127, inclusive. + * Stops at len, or NUL byte if len is SIZE_MAX. */ assert(str); - for (const char *p = str; *p; p++) - if ((unsigned char) *p >= 128) - return NULL; - - return (char*) str; -} - -char *ascii_is_valid_n(const char *str, size_t len) { - /* Very similar to ascii_is_valid(), but checks exactly len - * bytes and rejects any NULs in that range. */ - - assert(str); - - for (size_t i = 0; i < len; i++) - if ((unsigned char) str[i] >= 128 || str[i] == 0) + for (size_t i = 0; len != SIZE_MAX ? i < len : str[i] != '\0'; i++) + if ((unsigned char) str[i] >= 128 || str[i] == '\0') return NULL; return (char*) str; @@ -385,7 +359,7 @@ size_t utf8_encode_unichar(char *out_utf8, char32_t g) { return 0; } -char *utf16_to_utf8(const char16_t *s, size_t length /* bytes! */) { +char* utf16_to_utf8(const char16_t *s, size_t length /* bytes! */) { const uint8_t *f; char *r, *t; @@ -530,6 +504,10 @@ size_t char16_strlen(const char16_t *s) { return n; } +size_t char16_strsize(const char16_t *s) { + return s ? (char16_strlen(s) + 1) * sizeof(*s) : 0; +} + /* expected size used to encode one unicode char */ static int utf8_unichar_to_encoded_len(char32_t unichar) { @@ -610,11 +588,14 @@ size_t utf8_n_codepoints(const char *str) { } size_t utf8_console_width(const char *str) { - size_t n = 0; + + if (isempty(str)) + return 0; /* Returns the approximate width a string will take on screen when printed on a character cell * terminal/console. */ + size_t n = 0; while (*str) { int w; diff --git a/src/libnm-systemd-shared/src/basic/utf8.h b/src/libnm-systemd-shared/src/basic/utf8.h index 962312c5fb..221bc46a2d 100644 --- a/src/libnm-systemd-shared/src/basic/utf8.h +++ b/src/libnm-systemd-shared/src/basic/utf8.h @@ -14,31 +14,35 @@ bool unichar_is_valid(char32_t c); -char *utf8_is_valid_n(const char *str, size_t len_bytes) _pure_; -static inline char *utf8_is_valid(const char *s) { - return utf8_is_valid_n(s, SIZE_MAX); +char* utf8_is_valid_n(const char *str, size_t len_bytes) _pure_; +static inline char* utf8_is_valid(const char *str) { + return utf8_is_valid_n(str, SIZE_MAX); +} + +char* ascii_is_valid_n(const char *str, size_t len) _pure_; +static inline char* ascii_is_valid(const char *str) { + return ascii_is_valid_n(str, SIZE_MAX); } -char *ascii_is_valid(const char *s) _pure_; -char *ascii_is_valid_n(const char *str, size_t len); int utf8_to_ascii(const char *str, char replacement_char, char **ret); bool utf8_is_printable_newline(const char* str, size_t length, bool allow_newline) _pure_; #define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true) -char *utf8_escape_invalid(const char *s); -char *utf8_escape_non_printable_full(const char *str, size_t console_width, bool force_ellipsis); -static inline char *utf8_escape_non_printable(const char *str) { +char* utf8_escape_invalid(const char *s); +char* utf8_escape_non_printable_full(const char *str, size_t console_width, bool force_ellipsis); +static inline char* utf8_escape_non_printable(const char *str) { return utf8_escape_non_printable_full(str, SIZE_MAX, false); } size_t utf8_encode_unichar(char *out_utf8, char32_t g); size_t utf16_encode_unichar(char16_t *out, char32_t c); -char *utf16_to_utf8(const char16_t *s, size_t length /* bytes! */); +char* utf16_to_utf8(const char16_t *s, size_t length /* bytes! */); char16_t *utf8_to_utf16(const char *s, size_t length); size_t char16_strlen(const char16_t *s); /* returns the number of 16-bit words in the string (not bytes!) */ +size_t char16_strsize(const char16_t *s); int utf8_encoded_valid_unichar(const char *str, size_t length); int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar); @@ -56,4 +60,5 @@ static inline char32_t utf16_surrogate_pair_to_unichar(char16_t lead, char16_t t } size_t utf8_n_codepoints(const char *str); +int utf8_char_console_width(const char *str); size_t utf8_console_width(const char *str); diff --git a/src/libnm-systemd-shared/src/fundamental/iovec-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/iovec-util-fundamental.h new file mode 100644 index 0000000000..68d5bf4ee0 --- /dev/null +++ b/src/libnm-systemd-shared/src/fundamental/iovec-util-fundamental.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#if SD_BOOT +/* struct iovec is a POSIX userspace construct. Let's introduce it also in EFI mode, it's just so useful */ +struct iovec { + void *iov_base; + size_t iov_len; +}; + +static inline void free(void *p); +#endif + +/* This accepts both const and non-const pointers */ +#define IOVEC_MAKE(base, len) \ + (struct iovec) { \ + .iov_base = (void*) (base), \ + .iov_len = (len), \ + } + +static inline void iovec_done(struct iovec *iovec) { + /* A _cleanup_() helper that frees the iov_base in the iovec */ + assert(iovec); + + iovec->iov_base = mfree(iovec->iov_base); + iovec->iov_len = 0; +} + +static inline bool iovec_is_set(const struct iovec *iovec) { + /* Checks if the iovec points to a non-empty chunk of memory */ + return iovec && iovec->iov_len > 0 && iovec->iov_base; +} + +static inline bool iovec_is_valid(const struct iovec *iovec) { + /* Checks if the iovec is either NULL, empty or points to a valid bit of memory */ + return !iovec || (iovec->iov_base || iovec->iov_len == 0); +} diff --git a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h index 041649a03b..5eb31cd742 100644 --- a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h @@ -32,6 +32,10 @@ _Pragma("GCC diagnostic push"); \ _Pragma("GCC diagnostic ignored \"-Wshadow\"") +#define DISABLE_WARNING_STRINGOP_OVERREAD \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wstringop-overread\"") + #define DISABLE_WARNING_INCOMPATIBLE_POINTER_TYPES \ _Pragma("GCC diagnostic push"); \ _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"") @@ -44,6 +48,28 @@ _Pragma("GCC diagnostic push"); \ _Pragma("GCC diagnostic ignored \"-Waddress\"") +#define DISABLE_WARNING_STRINGOP_TRUNCATION \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"") + +#if HAVE_WARNING_ZERO_LENGTH_BOUNDS +# define DISABLE_WARNING_ZERO_LENGTH_BOUNDS \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wzero-length-bounds\"") +#else +# define DISABLE_WARNING_ZERO_LENGTH_BOUNDS \ + _Pragma("GCC diagnostic push") +#endif + +#if HAVE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT +# define DISABLE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wzero-as-null-pointer-constant\"") +#else +# define DISABLE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT \ + _Pragma("GCC diagnostic push") +#endif + #define REENABLE_WARNING \ _Pragma("GCC diagnostic pop") @@ -80,10 +106,10 @@ # define _alloc_(...) __attribute__((__alloc_size__(__VA_ARGS__))) #endif -#if __GNUC__ >= 7 || (defined(__clang__) && __clang_major__ >= 10) -# define _fallthrough_ __attribute__((__fallthrough__)) -#else +#if defined(__clang__) && __clang_major__ < 10 # define _fallthrough_ +#else +# define _fallthrough_ __attribute__((__fallthrough__)) #endif #define XSTRINGIFY(x) #x @@ -249,6 +275,30 @@ CONST_ISPOWEROF2(_x); \ })) +#define ADD_SAFE(ret, a, b) (!__builtin_add_overflow(a, b, ret)) +#define INC_SAFE(a, b) __INC_SAFE(UNIQ, a, b) +#define __INC_SAFE(q, a, b) \ + ({ \ + const typeof(a) UNIQ_T(A, q) = (a); \ + ADD_SAFE(UNIQ_T(A, q), *UNIQ_T(A, q), b); \ + }) + +#define SUB_SAFE(ret, a, b) (!__builtin_sub_overflow(a, b, ret)) +#define DEC_SAFE(a, b) __DEC_SAFE(UNIQ, a, b) +#define __DEC_SAFE(q, a, b) \ + ({ \ + const typeof(a) UNIQ_T(A, q) = (a); \ + SUB_SAFE(UNIQ_T(A, q), *UNIQ_T(A, q), b); \ + }) + +#define MUL_SAFE(ret, a, b) (!__builtin_mul_overflow(a, b, ret)) +#define MUL_ASSIGN_SAFE(a, b) __MUL_ASSIGN_SAFE(UNIQ, a, b) +#define __MUL_ASSIGN_SAFE(q, a, b) \ + ({ \ + const typeof(a) UNIQ_T(A, q) = (a); \ + MUL_SAFE(UNIQ_T(A, q), *UNIQ_T(A, q), b); \ + }) + #define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b)) #define __LESS_BY(aq, a, bq, b) \ ({ \ @@ -298,7 +348,7 @@ const typeof(y) UNIQ_T(A, q) = (y); \ const typeof(x) UNIQ_T(B, q) = DIV_ROUND_UP((x), UNIQ_T(A, q)); \ typeof(x) UNIQ_T(C, q); \ - __builtin_mul_overflow(UNIQ_T(B, q), UNIQ_T(A, q), &UNIQ_T(C, q)) ? (typeof(x)) -1 : UNIQ_T(C, q); \ + MUL_SAFE(&UNIQ_T(C, q), UNIQ_T(B, q), UNIQ_T(A, q)) ? UNIQ_T(C, q) : (typeof(x)) -1; \ }) #define ROUND_UP(x, y) __ROUND_UP(UNIQ, (x), (y)) @@ -493,6 +543,10 @@ static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { } \ } +/* Restriction/bug (see below) was fixed in GCC 15 and clang 19. */ +#if __GNUC__ >= 15 || (defined(__clang__) && __clang_major__ >= 19) +#define DECLARE_FLEX_ARRAY(type, name) type name[] +#else /* Declare a flexible array usable in a union. * This is essentially a work-around for a pointless constraint in C99 * and might go away in some future version of the standard. @@ -504,12 +558,12 @@ static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { dummy_t __empty__ ## name; \ type name[]; \ } +#endif /* Declares an ELF read-only string section that does not occupy memory at runtime. */ #define DECLARE_NOALLOC_SECTION(name, text) \ asm(".pushsection " name ",\"S\"\n\t" \ ".ascii " STRINGIFY(text) "\n\t" \ - ".zero 1\n\t" \ ".popsection\n") #ifdef SBAT_DISTRO @@ -517,3 +571,22 @@ static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { #else #define DECLARE_SBAT(text) #endif + +#define sizeof_field(struct_type, member) sizeof(((struct_type *) 0)->member) +#define endoffsetof_field(struct_type, member) (offsetof(struct_type, member) + sizeof_field(struct_type, member)) +#define voffsetof(v, member) offsetof(typeof(v), member) + +#define _FOREACH_ARRAY(i, array, num, m, end) \ + for (typeof(array[0]) *i = (array), *end = ({ \ + typeof(num) m = (num); \ + (i && m > 0) ? i + m : NULL; \ + }); end && i < end; i++) + +#define FOREACH_ARRAY(i, array, num) \ + _FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ)) + +#define FOREACH_ELEMENT(i, array) \ + FOREACH_ARRAY(i, array, ELEMENTSOF(array)) + +#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) +#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) diff --git a/src/libnm-systemd-shared/src/fundamental/sha256.c b/src/libnm-systemd-shared/src/fundamental/sha256-fundamental.c similarity index 98% rename from src/libnm-systemd-shared/src/fundamental/sha256.c rename to src/libnm-systemd-shared/src/fundamental/sha256-fundamental.c index 4447ad8a66..03381835d6 100644 --- a/src/libnm-systemd-shared/src/fundamental/sha256.c +++ b/src/libnm-systemd-shared/src/fundamental/sha256-fundamental.c @@ -21,15 +21,9 @@ License along with the GNU C Library; if not, see . */ -#include -#if SD_BOOT -# include "efi-string.h" -#else -# include -#endif - #include "macro-fundamental.h" -#include "sha256.h" +#include "memory-util-fundamental.h" +#include "sha256-fundamental.h" #include "unaligned-fundamental.h" #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ diff --git a/src/libnm-systemd-shared/src/fundamental/sha256.h b/src/libnm-systemd-shared/src/fundamental/sha256-fundamental.h similarity index 100% rename from src/libnm-systemd-shared/src/fundamental/sha256.h rename to src/libnm-systemd-shared/src/fundamental/sha256-fundamental.h diff --git a/src/libnm-systemd-shared/src/shared/dns-domain.c b/src/libnm-systemd-shared/src/shared/dns-domain.c index 909b4cdcc9..e91284177c 100644 --- a/src/libnm-systemd-shared/src/shared/dns-domain.c +++ b/src/libnm-systemd-shared/src/shared/dns-domain.c @@ -395,9 +395,9 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, } #endif -int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_ret) { - _cleanup_free_ char *ret = NULL; - size_t n = 0; +int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **ret) { + _cleanup_free_ char *result = NULL; + size_t n_result = 0, n_unescaped = 0; const char *p; bool first = true; int r; @@ -427,17 +427,18 @@ int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_r break; } + n_unescaped += r + !first; /* Count unescaped length to make max length determination below */ - if (_ret) { - if (!GREEDY_REALLOC(ret, n + !first + DNS_LABEL_ESCAPED_MAX)) + if (ret) { + if (!GREEDY_REALLOC(result, n_result + !first + DNS_LABEL_ESCAPED_MAX)) return -ENOMEM; - r = dns_label_escape(label, r, ret + n + !first, DNS_LABEL_ESCAPED_MAX); + r = dns_label_escape(label, r, result + n_result + !first, DNS_LABEL_ESCAPED_MAX); if (r < 0) return r; if (!first) - ret[n] = '.'; + result[n_result] = '.'; } else { char escaped[DNS_LABEL_ESCAPED_MAX]; @@ -446,44 +447,50 @@ int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_r return r; } - n += r + !first; + n_result += r + !first; first = false; } finish: - if (n > DNS_HOSTNAME_MAX) - return -EINVAL; + if (n_unescaped == 0) { + /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */ - if (_ret) { - if (n == 0) { - /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */ - if (!GREEDY_REALLOC(ret, 2)) + if (ret) { + if (!GREEDY_REALLOC(result, 2)) /* Room for dot, and already pre-allocate space for the trailing NUL byte at the same time */ return -ENOMEM; - ret[n++] = '.'; - } else { - if (!GREEDY_REALLOC(ret, n + 1)) - return -ENOMEM; + result[n_result++] = '.'; } - ret[n] = 0; - *_ret = TAKE_PTR(ret); + n_unescaped++; + } + + if (n_unescaped > DNS_HOSTNAME_MAX) /* Enforce max length check on unescaped length */ + return -EINVAL; + + if (ret) { + /* Suffix with a NUL byte */ + if (!GREEDY_REALLOC(result, n_result + 1)) + return -ENOMEM; + + result[n_result] = 0; + *ret = TAKE_PTR(result); } return 0; } -void dns_name_hash_func(const char *p, struct siphash *state) { +void dns_name_hash_func(const char *name, struct siphash *state) { int r; - assert(p); + assert(name); - for (;;) { + for (const char *p = name;;) { char label[DNS_LABEL_MAX+1]; r = dns_label_unescape(&p, label, sizeof label, 0); if (r < 0) - break; + return string_hash_func(p, state); /* fallback for invalid DNS names */ if (r == 0) break; @@ -509,13 +516,13 @@ int dns_name_compare_func(const char *a, const char *b) { for (;;) { char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; - if (x == NULL && y == NULL) + if (!x && !y) return 0; r = dns_label_unescape_suffix(a, &x, la, sizeof(la)); q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb)); if (r < 0 || q < 0) - return CMP(r, q); + return strcmp(a, b); /* if not valid DNS labels, then let's compare the whole strings as is */ r = ascii_strcasecmp_nn(la, r, lb, q); if (r != 0) @@ -753,7 +760,7 @@ int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_ad if (r > 0) { uint8_t a[4]; - for (size_t i = 0; i < ELEMENTSOF(a); i++) { + FOREACH_ELEMENT(i, a) { char label[DNS_LABEL_MAX+1]; r = dns_label_unescape(&p, label, sizeof label, 0); @@ -764,7 +771,7 @@ int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_ad if (r > 3) return -EINVAL; - r = safe_atou8(label, &a[i]); + r = safe_atou8(label, i); if (r < 0) return r; } @@ -898,6 +905,75 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo return out - buffer; } +/* Decode a domain name according to RFC 1035 Section 3.1, without compression */ +int dns_name_from_wire_format(const uint8_t **data, size_t *len, char **ret) { + _cleanup_free_ char *domain = NULL; + const uint8_t *optval; + size_t optlen, n = 0; + int r; + + assert(data); + assert(len); + assert(*data || *len == 0); + assert(ret); + + optval = *data; + optlen = *len; + + for (;;) { + const char *label; + uint8_t c; + + /* Unterminated name */ + if (optlen == 0) + return -EBADMSG; + + /* RFC 1035 § 3.1 total length of encoded name is limited to 255 octets */ + if (*len - optlen > 255) + return -EMSGSIZE; + + c = *optval; + optval++; + optlen--; + + if (c == 0) + /* End label */ + break; + if (c > DNS_LABEL_MAX) + return -EBADMSG; + if (c > optlen) + return -EMSGSIZE; + + /* Literal label */ + label = (const char*) optval; + optval += c; + optlen -= c; + + if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (n != 0) + domain[n++] = '.'; + + r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + } + + if (!GREEDY_REALLOC(domain, n + 1)) + return -ENOMEM; + + domain[n] = '\0'; + + *ret = TAKE_PTR(domain); + *data = optval; + *len = optlen; + + return n; +} + static bool srv_type_label_is_valid(const char *label, size_t n) { assert(label); @@ -980,6 +1056,29 @@ bool dns_service_name_is_valid(const char *name) { return true; } +bool dns_subtype_name_is_valid(const char *name) { + size_t l; + + /* This more or less implements RFC 6763, Section 7.2 */ + + if (!name) + return false; + + if (!utf8_is_valid(name)) + return false; + + if (string_has_cc(name, NULL)) + return false; + + l = strlen(name); + if (l <= 0) + return false; + if (l > DNS_LABEL_MAX) + return false; + + return true; +} + int dns_service_join(const char *name, const char *type, const char *domain, char **ret) { char escaped[DNS_LABEL_ESCAPED_MAX]; _cleanup_free_ char *n = NULL; diff --git a/src/libnm-systemd-shared/src/shared/dns-domain.h b/src/libnm-systemd-shared/src/shared/dns-domain.h index 331fb89637..edfb2f00ca 100644 --- a/src/libnm-systemd-shared/src/shared/dns-domain.h +++ b/src/libnm-systemd-shared/src/shared/dns-domain.h @@ -79,10 +79,12 @@ bool dns_name_is_root(const char *name); bool dns_name_is_single_label(const char *name); int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical); +int dns_name_from_wire_format(const uint8_t **data, size_t *len, char **ret); bool dns_srv_type_is_valid(const char *name); bool dnssd_srv_type_is_valid(const char *name); bool dns_service_name_is_valid(const char *name); +bool dns_subtype_name_is_valid(const char *name); int dns_service_join(const char *name, const char *type, const char *domain, char **ret); int dns_service_split(const char *joined, char **ret_name, char **ret_type, char **ret_domain);