From 3ae6505d7d1bc575152f411332073c10af0dff4d Mon Sep 17 00:00:00 2001 From: Jan Vaclav Date: Wed, 22 Jan 2025 18:45:03 +0100 Subject: [PATCH 01/78] systemd: update code from upstream (2025-05-05) 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=a50fa2a40f4a91d49503d3588a3dd29ea05e559b ( 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.c" 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/assert-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_fs.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_syscall.h" nm_copy_sd_shared "src/basic/missing_wait.h" nm_copy_sd_shared "src/basic/mountpoint-util.c" nm_copy_sd_shared "src/basic/mountpoint-util.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.c" 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/basic/include/net/if.h" nm_copy_sd_shared "src/basic/include/netinet/in.h" nm_copy_sd_shared "src/fundamental/assert-fundamental.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" --- .../src/libsystemd-network/dhcp6-internal.h | 2 +- .../libsystemd-network/dhcp6-lease-internal.h | 2 +- .../src/libsystemd-network/dhcp6-network.c | 2 +- .../src/libsystemd-network/sd-dhcp6-client.c | 5 +- .../libsystemd/sd-device/device-internal.h | 94 +- .../src/libsystemd/sd-device/device-private.c | 31 +- .../src/libsystemd/sd-device/device-private.h | 8 +- .../src/libsystemd/sd-device/device-util.c | 150 ++ .../src/libsystemd/sd-device/device-util.h | 17 +- .../src/libsystemd/sd-device/sd-device.c | 572 ++++---- .../src/libsystemd/sd-event/event-util.c | 158 ++- .../src/libsystemd/sd-event/event-util.h | 9 + .../src/libsystemd/sd-event/sd-event.c | 128 +- .../src/libsystemd/sd-id128/id128-util.c | 1 + .../src/libsystemd/sd-id128/sd-id128.c | 13 +- .../src/systemd/_sd-common.h | 2 +- .../src/systemd/sd-device.h | 3 +- .../src/systemd/sd-dhcp6-client.h | 3 +- .../src/systemd/sd-dhcp6-lease.h | 3 +- .../src/systemd/sd-dhcp6-option.h | 3 +- src/libnm-systemd-core/src/systemd/sd-ndisc.h | 3 +- .../src/basic/alloc-util.c | 1 - .../src/basic/alloc-util.h | 13 +- .../src/basic/assert-util.h | 30 + src/libnm-systemd-shared/src/basic/bitfield.h | 1 + .../src/basic/cgroup-util.h | 10 +- .../src/basic/chattr-util.c | 2 +- .../src/basic/chattr-util.h | 13 +- .../src/basic/devnum-util.h | 10 +- src/libnm-systemd-shared/src/basic/env-file.c | 1 + src/libnm-systemd-shared/src/basic/env-util.c | 41 +- src/libnm-systemd-shared/src/basic/env-util.h | 2 - .../src/basic/errno-util.h | 1 + src/libnm-systemd-shared/src/basic/escape.c | 25 +- src/libnm-systemd-shared/src/basic/escape.h | 9 +- .../src/basic/ether-addr-util.c | 17 +- .../src/basic/ether-addr-util.h | 1 - src/libnm-systemd-shared/src/basic/fd-util.c | 41 +- src/libnm-systemd-shared/src/basic/fd-util.h | 1 + src/libnm-systemd-shared/src/basic/fileio.c | 227 ++- src/libnm-systemd-shared/src/basic/fileio.h | 12 +- .../src/basic/format-ifname.c | 2 + .../src/basic/format-util.c | 1 - src/libnm-systemd-shared/src/basic/fs-util.c | 15 +- src/libnm-systemd-shared/src/basic/fs-util.h | 9 + .../src/basic/glyph-util.c | 206 +-- .../src/basic/glyph-util.h | 128 +- .../src/basic/hash-funcs.c | 78 +- .../src/basic/hash-funcs.h | 3 + src/libnm-systemd-shared/src/basic/hashmap.c | 40 +- src/libnm-systemd-shared/src/basic/hashmap.h | 89 +- .../src/basic/hexdecoct.h | 14 +- .../src/basic/hostname-util.c | 50 +- .../src/basic/hostname-util.h | 36 +- .../src/basic/in-addr-util.c | 18 +- .../src/basic/include/net/if.h | 9 + .../src/basic/include/netinet/in.h | 19 + src/libnm-systemd-shared/src/basic/io-util.c | 7 +- src/libnm-systemd-shared/src/basic/io-util.h | 9 +- .../src/basic/iovec-util.h | 8 +- src/libnm-systemd-shared/src/basic/list.h | 6 +- .../src/basic/locale-util.c | 120 +- .../src/basic/lock-util.h | 2 + src/libnm-systemd-shared/src/basic/log.h | 196 +-- src/libnm-systemd-shared/src/basic/macro.h | 131 -- .../src/basic/memory-util.c | 16 +- .../src/basic/memory-util.h | 88 +- src/libnm-systemd-shared/src/basic/mempool.c | 1 + .../src/basic/missing_fcntl.h | 71 +- .../src/basic/missing_fs.h | 14 + .../src/basic/missing_random.h | 21 +- .../src/basic/missing_socket.h | 57 +- .../src/basic/missing_stat.h | 135 -- .../src/basic/missing_syscall.h | 479 +------ .../src/basic/missing_threads.h | 13 - .../src/basic/missing_type.h | 12 - .../src/basic/missing_wait.h | 1 + .../src/basic/mountpoint-util.c | 819 +++++++++++ .../src/basic/mountpoint-util.h | 87 ++ .../src/basic/namespace-util.h | 5 +- .../src/basic/ordered-set.h | 21 - .../src/basic/parse-util.c | 14 +- .../src/basic/parse-util.h | 16 +- .../src/basic/path-util.c | 71 +- .../src/basic/path-util.h | 1 + .../src/basic/pidfd-util.c | 55 +- src/libnm-systemd-shared/src/basic/pidref.h | 22 +- src/libnm-systemd-shared/src/basic/prioq.c | 12 + .../src/basic/process-util.c | 263 ++-- .../src/basic/process-util.h | 47 +- .../src/basic/random-util.c | 3 +- .../src/basic/random-util.h | 8 +- .../src/basic/ratelimit.h | 52 + src/libnm-systemd-shared/src/basic/set.h | 31 +- .../src/basic/signal-util.c | 6 +- .../src/basic/signal-util.h | 1 + .../src/basic/socket-util.c | 92 +- .../src/basic/socket-util.h | 61 +- .../src/basic/stat-util.c | 91 +- .../src/basic/stat-util.h | 27 +- .../src/basic/stdio-util.h | 1 + .../src/basic/string-util.c | 112 +- .../src/basic/string-util.h | 18 +- src/libnm-systemd-shared/src/basic/strv.c | 55 +- src/libnm-systemd-shared/src/basic/strv.h | 10 +- .../src/basic/time-util.c | 5 +- .../src/basic/time-util.h | 4 +- .../src/basic/tmpfile-util.c | 1 + .../src/basic/user-util.c | 1239 +++++++++++++++++ src/libnm-systemd-shared/src/basic/utf8.c | 25 + src/libnm-systemd-shared/src/basic/utf8.h | 3 +- .../src/fundamental/assert-fundamental.h | 102 ++ .../src/fundamental/iovec-util-fundamental.h | 12 +- .../src/fundamental/macro-fundamental.h | 184 +-- .../src/fundamental/memory-util-fundamental.h | 115 +- .../src/fundamental/sha256-fundamental.c | 7 + .../src/fundamental/string-util-fundamental.c | 2 +- .../src/fundamental/string-util-fundamental.h | 3 + .../src/shared/dns-domain.c | 14 +- 119 files changed, 4603 insertions(+), 2885 deletions(-) create mode 100644 src/libnm-systemd-core/src/libsystemd/sd-device/device-util.c create mode 100644 src/libnm-systemd-shared/src/basic/assert-util.h create mode 100644 src/libnm-systemd-shared/src/basic/include/net/if.h create mode 100644 src/libnm-systemd-shared/src/basic/include/netinet/in.h create mode 100644 src/libnm-systemd-shared/src/basic/missing_fs.h delete mode 100644 src/libnm-systemd-shared/src/basic/missing_stat.h delete mode 100644 src/libnm-systemd-shared/src/basic/missing_threads.h delete mode 100644 src/libnm-systemd-shared/src/basic/missing_type.h create mode 100644 src/libnm-systemd-shared/src/basic/mountpoint-util.c create mode 100644 src/libnm-systemd-shared/src/basic/mountpoint-util.h create mode 100644 src/libnm-systemd-shared/src/basic/user-util.c create mode 100644 src/libnm-systemd-shared/src/fundamental/assert-fundamental.h 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 ecd62ea802..2ef8dc8690 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h @@ -8,8 +8,8 @@ #include #include -#include "sd-event.h" #include "sd-dhcp6-client.h" +#include "sd-event.h" #include "dhcp-duid-internal.h" #include "dhcp6-client-internal.h" 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 60cd84f2d8..62c3858841 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,10 +8,10 @@ #include #include "sd-dhcp6-lease.h" -#include "dns-resolver-internal.h" #include "dhcp6-option.h" #include "dhcp6-protocol.h" +#include "dns-resolver-internal.h" #include "macro.h" #include "set.h" #include "time-util.h" 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 0aa8469cc3..dbe4253164 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c @@ -4,13 +4,13 @@ ***/ #include +#include #include #include #include #include #include #include -#include #include "dhcp6-internal.h" #include "dhcp6-protocol.h" 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 3e992d7cad..b806a7debb 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 @@ -4,9 +4,9 @@ ***/ #include -#include #include #include +#include #include "sd-dhcp6-client.h" @@ -1289,7 +1289,8 @@ static int client_receive_message( sd_dhcp6_client *client = ASSERT_PTR(userdata); DHCP6_CLIENT_DONT_DESTROY(client); - /* This needs to be initialized with zero. See #20741. */ + /* This needs to be initialized with zero. See #20741. + * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL) control = {}; struct iovec iov; union sockaddr_union sa = {}; diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h b/src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h index a465eb25fd..cbd89849bd 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-internal.h @@ -13,6 +13,43 @@ struct sd_device { unsigned n_ref; + /* syspath */ + char *syspath; + const char *devpath; + const char *sysnum; + char *sysname; + + /* only set when device is passed through netlink */ + sd_device_action_t action; + uint64_t seqnum; + + /* basic kernel properties */ + char *subsystem; + char *driver_subsystem; /* only set for the 'drivers' subsystem */ + char *driver; + char *devtype; + + /* device node properties */ + char *devname; + dev_t devnum; + mode_t devmode; + uid_t devuid; + gid_t devgid; + + /* block device properties */ + uint64_t diskseq; /* Block device sequence number, monothonically incremented by the kernel on create/attach */ + + /* network interface properties */ + int ifindex; + + /* determined by devnnum, ifindex, subsystem, and sysname */ + char *device_id; + + /* sysfs attributes */ + Hashmap *sysattr_values; /* cached sysattr values */ + Set *sysattrs; /* names of sysattrs */ + Iterator sysattrs_iterator; + /* The database version indicates the supported features by the udev database. * This is saved and parsed in V field. * @@ -21,68 +58,38 @@ struct sd_device { */ unsigned database_version; - sd_device *parent; + /* when device is initialized by udevd */ + usec_t usec_initialized; - OrderedHashmap *properties; + /* properties */ + OrderedHashmap *properties; /* all properties set from uevent and by udevd */ Iterator properties_iterator; uint64_t properties_generation; /* changes whenever the properties are changed */ uint64_t properties_iterator_generation; /* generation when iteration was started */ + OrderedHashmap *properties_db; /* the subset of the properties that should be written to the db */ + char **properties_strv; /* the properties hashmap as a strv */ + char *properties_nulstr; /* the same as a nulstr */ + size_t properties_nulstr_len; - /* the subset of the properties that should be written to the db */ - OrderedHashmap *properties_db; - - Hashmap *sysattr_values; /* cached sysattr values */ - - Set *sysattrs; /* names of sysattrs */ - Iterator sysattrs_iterator; - + /* TAG keyword */ Set *all_tags, *current_tags; Iterator all_tags_iterator, current_tags_iterator; uint64_t all_tags_iterator_generation, current_tags_iterator_generation; /* generation when iteration was started */ uint64_t tags_generation; /* changes whenever the tags are changed */ + /* SYMLINK keyword */ Set *devlinks; Iterator devlinks_iterator; uint64_t devlinks_generation; /* changes whenever the devlinks are changed */ uint64_t devlinks_iterator_generation; /* generation when iteration was started */ int devlink_priority; + /* parent and child devices */ + sd_device *parent; Hashmap *children; Iterator children_iterator; bool children_enumerated; - int ifindex; - char *devtype; - char *devname; - dev_t devnum; - - char **properties_strv; /* the properties hashmap as a strv */ - char *properties_nulstr; /* the same as a nulstr */ - size_t properties_nulstr_len; - - char *syspath; - const char *devpath; - const char *sysnum; - char *sysname; - - char *subsystem; - char *driver_subsystem; /* only set for the 'drivers' subsystem */ - char *driver; - - char *device_id; - - usec_t usec_initialized; - - mode_t devmode; - uid_t devuid; - gid_t devgid; - - uint64_t diskseq; /* Block device sequence number, monothonically incremented by the kernel on create/attach */ - - /* only set when device is passed through netlink */ - sd_device_action_t action; - uint64_t seqnum; - bool parent_set:1; /* no need to try to reload parent */ bool sysattrs_read:1; /* don't try to re-read sysattrs once read */ bool property_tags_outdated:1; /* need to update TAGS= or CURRENT_TAGS= property */ @@ -92,7 +99,6 @@ struct sd_device { bool driver_set:1; /* don't reread driver */ bool uevent_loaded:1; /* don't reread uevent */ bool db_loaded; /* don't reread db */ - bool is_initialized:1; bool sealed:1; /* don't read more information from uevent/db */ bool db_persist:1; /* don't clean up the db when switching from initrd to real root */ @@ -106,6 +112,8 @@ static inline int device_add_property_internal(sd_device *device, const char *ke int device_set_syspath(sd_device *device, const char *_syspath, bool verify); int device_set_ifindex(sd_device *device, const char *ifindex); +int device_set_devuid(sd_device *device, const char *uid); +int device_set_devgid(sd_device *device, const char *gid); int device_set_devmode(sd_device *device, const char *devmode); int device_set_devname(sd_device *device, const char *devname); int device_set_devtype(sd_device *device, const char *devtype); 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 1c148b8573..3ce87690b3 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 @@ -116,6 +116,10 @@ int device_get_devnode_mode(sd_device *device, mode_t *ret) { assert(device); + r = device_read_uevent_file(device); + if (r < 0) + return r; + r = device_read_db(device); if (r < 0) return r; @@ -134,6 +138,10 @@ int device_get_devnode_uid(sd_device *device, uid_t *ret) { assert(device); + r = device_read_uevent_file(device); + if (r < 0) + return r; + r = device_read_db(device); if (r < 0) return r; @@ -147,7 +155,7 @@ int device_get_devnode_uid(sd_device *device, uid_t *ret) { return 0; } -static int device_set_devuid(sd_device *device, const char *uid) { +int device_set_devuid(sd_device *device, const char *uid) { uid_t u; int r; @@ -172,6 +180,10 @@ int device_get_devnode_gid(sd_device *device, gid_t *ret) { assert(device); + r = device_read_uevent_file(device); + if (r < 0) + return r; + r = device_read_db(device); if (r < 0) return r; @@ -185,7 +197,7 @@ int device_get_devnode_gid(sd_device *device, gid_t *ret) { return 0; } -static int device_set_devgid(sd_device *device, const char *gid) { +int device_set_devgid(sd_device *device, const char *gid) { gid_t g; int r; @@ -429,10 +441,11 @@ static int device_verify(sd_device *device) { return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL), "sd-device: Device created from strv or nulstr lacks devpath, subsystem, action or seqnum."); - if (streq(device->subsystem, "drivers")) { + if (device_in_subsystem(device, "drivers")) { r = device_set_drivers_subsystem(device); if (r < 0) - return r; + return log_device_debug_errno(device, r, + "sd-device: Failed to set driver subsystem: %m"); } device->sealed = true; @@ -679,8 +692,8 @@ int device_clone_with_db(sd_device *device, sd_device **ret) { void device_cleanup_tags(sd_device *device) { assert(device); - device->all_tags = set_free_free(device->all_tags); - device->current_tags = set_free_free(device->current_tags); + device->all_tags = set_free(device->all_tags); + device->current_tags = set_free(device->current_tags); device->property_tags_outdated = true; device->tags_generation++; } @@ -688,7 +701,7 @@ void device_cleanup_tags(sd_device *device) { void device_cleanup_devlinks(sd_device *device) { assert(device); - set_free_free(device->devlinks); + set_free(device->devlinks); device->devlinks = NULL; device->property_devlinks_outdated = true; device->devlinks_generation++; @@ -948,7 +961,3 @@ static const char* const device_action_table[_SD_DEVICE_ACTION_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(device_action, sd_device_action_t); - -void dump_device_action_table(void) { - DUMP_STRING_TABLE(device_action, sd_device_action_t, _SD_DEVICE_ACTION_MAX); -} 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 eab54203f0..dea7728633 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 @@ -9,6 +9,7 @@ #include "sd-device.h" +#include "chase.h" #include "macro.h" int device_new_from_mode_and_devnum(sd_device **ret, mode_t mode, dev_t devnum); @@ -17,8 +18,10 @@ int device_new_from_strv(sd_device **ret, char **strv); int device_opendir(sd_device *device, const char *subdir, DIR **ret); +int device_get_sysnum_unsigned(sd_device *device, unsigned *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_ifname(sd_device *device, const char **ret); int device_get_sysattr_int(sd_device *device, const char *sysattr, int *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) { @@ -31,9 +34,9 @@ int device_get_devnode_mode(sd_device *device, mode_t *ret); int device_get_devnode_uid(sd_device *device, uid_t *ret); int device_get_devnode_gid(sd_device *device, gid_t *ret); +int device_chase(sd_device *device, const char *path, ChaseFlags flags, char **ret_resolved, int *ret_fd); void device_clear_sysattr_cache(sd_device *device); -int device_cache_sysattr_value(sd_device *device, const char *key, char *value); -int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value); +int device_cache_sysattr_value(sd_device *device, char *key, char *value, int error); void device_seal(sd_device *device); void device_set_is_initialized(sd_device *device); @@ -76,4 +79,3 @@ 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_; -void dump_device_action_table(void); diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.c b/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.c new file mode 100644 index 0000000000..123629c356 --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.c @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "device-private.h" +#include "device-util.h" +#include "devnum-util.h" +#include "fd-util.h" +#include "string-util.h" +#include "strv.h" + +int devname_from_devnum(mode_t mode, dev_t devnum, char **ret) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + const char *devname; + int r; + + assert(ret); + + if (devnum_is_zero(devnum)) + return device_path_make_inaccessible(mode, ret); + + r = device_new_from_mode_and_devnum(&dev, mode, devnum); + if (r < 0) + return r; + + r = sd_device_get_devname(dev, &devname); + if (r < 0) + return r; + + return strdup_to(ret, devname); +} + +int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret_devname) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + r = device_new_from_mode_and_devnum(&dev, mode, devnum); + if (r < 0) + return r; + + fd = sd_device_open(dev, flags); + if (fd < 0) + return fd; + + if (ret_devname) { + const char *devname; + + r = sd_device_get_devname(dev, &devname); + if (r < 0) + return r; + + r = strdup_to(ret_devname, devname); + if (r < 0) + return r; + } + + return TAKE_FD(fd); +} + +static int add_string_field( + sd_device *device, + const char *field, + int (*func)(sd_device *dev, const char **s), + char ***strv) { + + const char *s; + int r; + + assert(device); + assert(field); + assert(func); + assert(strv); + + r = func(device, &s); + if (r < 0 && r != -ENOENT) + log_device_debug_errno(device, r, "Failed to get device \"%s\" property, ignoring: %m", field); + if (r >= 0) + (void) strv_extend_assignment(strv, field, s); + + return 0; +} + +char** device_make_log_fields(sd_device *device) { + _cleanup_strv_free_ char **strv = NULL; + dev_t devnum; + int ifindex; + sd_device_action_t action; + uint64_t seqnum, diskseq; + int r; + + assert(device); + + (void) add_string_field(device, "SYSPATH", sd_device_get_syspath, &strv); + (void) add_string_field(device, "SUBSYSTEM", sd_device_get_subsystem, &strv); + (void) add_string_field(device, "DEVTYPE", sd_device_get_devtype, &strv); + (void) add_string_field(device, "DRIVER", sd_device_get_driver, &strv); + (void) add_string_field(device, "DEVPATH", sd_device_get_devpath, &strv); + (void) add_string_field(device, "DEVNAME", sd_device_get_devname, &strv); + (void) add_string_field(device, "SYSNAME", sd_device_get_sysname, &strv); + (void) add_string_field(device, "SYSNUM", sd_device_get_sysnum, &strv); + + r = sd_device_get_devnum(device, &devnum); + if (r < 0 && r != -ENOENT) + log_device_debug_errno(device, r, "Failed to get device \"DEVNUM\" property, ignoring: %m"); + if (r >= 0) + (void) strv_extendf(&strv, "DEVNUM="DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(devnum)); + + r = sd_device_get_ifindex(device, &ifindex); + if (r < 0 && r != -ENOENT) + log_device_debug_errno(device, r, "Failed to get device \"IFINDEX\" property, ignoring: %m"); + if (r >= 0) + (void) strv_extendf(&strv, "IFINDEX=%i", ifindex); + + r = sd_device_get_action(device, &action); + if (r < 0 && r != -ENOENT) + log_device_debug_errno(device, r, "Failed to get device \"ACTION\" property, ignoring: %m"); + if (r >= 0) + (void) strv_extendf(&strv, "ACTION=%s", device_action_to_string(action)); + + r = sd_device_get_seqnum(device, &seqnum); + if (r < 0 && r != -ENOENT) + log_device_debug_errno(device, r, "Failed to get device \"SEQNUM\" property, ignoring: %m"); + if (r >= 0) + (void) strv_extendf(&strv, "SEQNUM=%"PRIu64, seqnum); + + r = sd_device_get_diskseq(device, &diskseq); + if (r < 0 && r != -ENOENT) + log_device_debug_errno(device, r, "Failed to get device \"DISKSEQ\" property, ignoring: %m"); + if (r >= 0) + (void) strv_extendf(&strv, "DISKSEQ=%"PRIu64, diskseq); + + return TAKE_PTR(strv); +} + +bool device_in_subsystem(sd_device *device, const char *subsystem) { + const char *s = NULL; + + assert(device); + + (void) sd_device_get_subsystem(device, &s); + return streq_ptr(s, subsystem); +} + +bool device_is_devtype(sd_device *device, const char *devtype) { + const char *s = NULL; + + assert(device); + + (void) sd_device_get_devtype(device, &s); + return streq_ptr(s, devtype); +} 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 b17993d554..070e564a52 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 @@ -110,6 +110,19 @@ bool device_is_devtype(sd_device *device, const char *devtype); static inline bool device_property_can_set(const char *property) { return property && !STR_IN_SET(property, - "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER", - "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS"); + /* basic properties set by kernel, only in netlink event */ + "ACTION", "SEQNUM", "SYNTH_UUID", + /* basic properties set by kernel, both in netlink event and uevent file */ + "DEVPATH", "DEVPATH_OLD", "SUBSYSTEM", "DEVTYPE", "DRIVER", "MODALIAS", + /* device node */ + "DEVNAME", "DEVMODE", "DEVUID", "DEVGID", "MAJOR", "MINOR", + /* block device */ + "DISKSEQ", "PARTN", + /* network interface (INTERFACE_OLD is set by udevd) */ + "IFINDEX", "INTERFACE", "INTERFACE_OLD", + /* basic properties set by udevd */ + "DEVLINKS", "TAGS", "CURRENT_TAGS", "USEC_INITIALIZED", "UDEV_DATABASE_VERSION") && + /* Similar to SYNTH_UUID, but set based on KEY=VALUE arguments passed by userspace. + * See kernel's f36776fafbaa0094390dd4e7e3e29805e0b82730 (v4.13) */ + !startswith(property, "SYNTH_ARG_"); } 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 a608ae326f..98a10d5b1f 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 @@ -366,7 +366,7 @@ _public_ int sd_device_new_from_ifindex(sd_device **ret, int ifindex) { assert_return(ret, -EINVAL); assert_return(ifindex > 0, -EINVAL); - r = rtnl_get_ifname_full(NULL, ifindex, &ifname, NULL); + r = rtnl_get_ifname(NULL, ifindex, &ifname); if (r < 0) return r; @@ -507,7 +507,7 @@ _public_ int sd_device_new_from_subsystem_sysname( if (streq(sep, "drivers")) /* If the sysname is "drivers", then it's the drivers directory itself that is meant. */ r = device_new_from_path_join(&device, subsystem, subsys, "drivers", "/sys/bus/", subsys, "/drivers", NULL); else - r = device_new_from_path_join(&device, subsystem, subsys, sep, "/sys/bus/", subsys, "/drivers/", sep); + r = device_new_from_path_join(&device, subsystem, subsys, sysname + (sep - name), "/sys/bus/", subsys, "/drivers/", sep); if (r < 0) return r; } @@ -766,16 +766,24 @@ static int handle_uevent_line( assert(major); assert(minor); + if (streq(key, "SUBSYSTEM")) + return device_set_subsystem(device, value); if (streq(key, "DEVTYPE")) return device_set_devtype(device, value); if (streq(key, "IFINDEX")) return device_set_ifindex(device, value); if (streq(key, "DEVNAME")) return device_set_devname(device, value); + if (streq(key, "DEVUID")) + return device_set_devuid(device, value); + if (streq(key, "DEVGID")) + return device_set_devgid(device, value); if (streq(key, "DEVMODE")) return device_set_devmode(device, value); if (streq(key, "DISKSEQ")) return device_set_diskseq(device, value); + if (streq(key, "DRIVER")) + return device_set_driver(device, value); if (streq(key, "MAJOR")) *major = value; else if (streq(key, "MINOR")) @@ -787,89 +795,59 @@ static int handle_uevent_line( } int device_read_uevent_file(sd_device *device) { - _cleanup_free_ char *uevent = NULL; - const char *syspath, *key = NULL, *value = NULL, *major = NULL, *minor = NULL; - char *path; - size_t uevent_len; int r; - enum { - PRE_KEY, - KEY, - PRE_VALUE, - VALUE, - INVALID_LINE, - } state = PRE_KEY; - assert(device); if (device->uevent_loaded || device->sealed) return 0; - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - device->uevent_loaded = true; - path = strjoina(syspath, "/uevent"); - - r = read_full_virtual_file(path, &uevent, &uevent_len); - if (r == -EACCES || ERRNO_IS_NEG_DEVICE_ABSENT(r)) + const char *uevent; + r = sd_device_get_sysattr_value(device, "uevent", &uevent); + if (ERRNO_IS_NEG_PRIVILEGE(r) || ERRNO_IS_NEG_DEVICE_ABSENT(r)) /* The uevent files may be write-only, the device may be already removed, or the device * may not have the uevent file. */ return 0; if (r < 0) - return log_device_debug_errno(device, r, "sd-device: Failed to read uevent file '%s': %m", path); + return log_device_debug_errno(device, r, "sd-device: Failed to read uevent file: %m"); - for (size_t i = 0; i < uevent_len; i++) - switch (state) { - case PRE_KEY: - if (!strchr(NEWLINE, uevent[i])) { - key = &uevent[i]; + _cleanup_strv_free_ char **v = NULL; + r = strv_split_newlines_full(&v, uevent, EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return log_device_debug_errno(device, r, "sd-device: Failed to parse uevent file: %m"); - state = KEY; - } - - break; - case KEY: - if (uevent[i] == '=') { - uevent[i] = '\0'; - - state = PRE_VALUE; - } else if (strchr(NEWLINE, uevent[i])) { - uevent[i] = '\0'; - log_device_debug(device, "sd-device: Invalid uevent line '%s', ignoring", key); - - state = PRE_KEY; - } - - break; - case PRE_VALUE: - value = &uevent[i]; - state = VALUE; - - _fallthrough_; /* to handle empty property */ - case VALUE: - if (strchr(NEWLINE, uevent[i])) { - uevent[i] = '\0'; - - r = handle_uevent_line(device, key, value, &major, &minor); - if (r < 0) - log_device_debug_errno(device, r, "sd-device: Failed to handle uevent entry '%s=%s', ignoring: %m", key, value); - - state = PRE_KEY; - } - - break; - default: - assert_not_reached(); + const char *major = NULL, *minor = NULL; + STRV_FOREACH(s, v) { + char *eq = strchr(*s, '='); + if (!eq) { + log_device_debug(device, "sd-device: Invalid uevent line, ignoring: %s", *s); + continue; } + *eq = '\0'; + + r = handle_uevent_line(device, *s, eq + 1, &major, &minor); + if (r < 0) + log_device_debug_errno(device, r, + "sd-device: Failed to handle uevent entry '%s=%s', ignoring: %m", + *s, eq + 1); + } + if (major) { r = device_set_devnum(device, major, minor); if (r < 0) - log_device_debug_errno(device, r, "sd-device: Failed to set 'MAJOR=%s' or 'MINOR=%s' from '%s', ignoring: %m", major, strna(minor), path); + log_device_debug_errno(device, r, + "sd-device: Failed to set 'MAJOR=%s' and/or 'MINOR=%s' from uevent, ignoring: %m", + major, strna(minor)); + } + + if (device_in_subsystem(device, "drivers")) { + r = device_set_drivers_subsystem(device); + if (r < 0) + log_device_debug_errno(device, r, + "sd-device: Failed to set driver subsystem, ignoring: %m"); } return 0; @@ -893,6 +871,21 @@ _public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) { return 0; } +int device_get_ifname(sd_device *device, const char **ret) { + int r; + + assert_return(device, -EINVAL); + + /* First, check if the device is a network interface. */ + r = sd_device_get_ifindex(device, NULL); + if (r < 0) + return r; + + /* The sysname and ifname may be different, as '!' in sysname are replaced with '/'. + * For network interfaces, we can use INTERFACE property. */ + return sd_device_get_property_value(device, "INTERFACE", ret); +} + _public_ int sd_device_new_from_device_id(sd_device **ret, const char *id) { int r; @@ -1214,36 +1207,29 @@ _public_ int sd_device_get_subsystem(sd_device *device, const char **ret) { assert_return(device, -EINVAL); + r = device_read_uevent_file(device); + if (r < 0) + return r; + if (!device->subsystem_set) { - _cleanup_free_ char *subsystem = NULL; - const char *syspath; - char *path; + const char *subsystem; - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - /* read 'subsystem' link */ - path = strjoina(syspath, "/subsystem"); - r = readlink_value(path, &subsystem); + r = sd_device_get_sysattr_value(device, "subsystem", &subsystem); if (r < 0 && r != -ENOENT) return log_device_debug_errno(device, r, "sd-device: Failed to read subsystem for %s: %m", device->devpath); - - if (subsystem) + if (r >= 0) r = device_set_subsystem(device, subsystem); /* use implicit names */ else if (!isempty(path_startswith(device->devpath, "/module/"))) r = device_set_subsystem(device, "module"); - else if (strstr(syspath, "/drivers/") || endswith(syspath, "/drivers")) + else if (strstr(device->devpath, "/drivers/") || endswith(device->devpath, "/drivers")) r = device_set_drivers_subsystem(device); else if (!isempty(PATH_STARTSWITH_SET(device->devpath, "/class/", "/bus/"))) r = device_set_subsystem(device, "subsystem"); - else { - device->subsystem_set = true; - r = 0; - } + else + r = device_set_subsystem(device, NULL); if (r < 0) return log_device_debug_errno(device, r, "sd-device: Failed to set subsystem for %s: %m", @@ -1352,23 +1338,21 @@ int device_set_driver(sd_device *device, const char *driver) { } _public_ int sd_device_get_driver(sd_device *device, const char **ret) { + int r; + assert_return(device, -EINVAL); + r = device_read_uevent_file(device); + if (r < 0) + return r; + if (!device->driver_set) { - _cleanup_free_ char *driver = NULL; - const char *syspath; - char *path; - int r; + const char *driver = NULL; - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - path = strjoina(syspath, "/driver"); - r = readlink_value(path, &driver); + r = sd_device_get_sysattr_value(device, "driver", &driver); if (r < 0 && r != -ENOENT) return log_device_debug_errno(device, r, - "sd-device: readlink(\"%s\") failed: %m", path); + "sd-device: Failed to read driver: %m"); r = device_set_driver(device, driver); if (r < 0) @@ -1476,6 +1460,26 @@ _public_ int sd_device_get_sysnum(sd_device *device, const char **ret) { return 0; } +int device_get_sysnum_unsigned(sd_device *device, unsigned *ret) { + int r; + + assert(device); + + const char *s; + r = sd_device_get_sysnum(device, &s); + if (r < 0) + return r; + + unsigned n; + r = safe_atou_full(s, SAFE_ATO_REFUSE_PLUS_MINUS | SAFE_ATO_REFUSE_LEADING_WHITESPACE | 10, &n); + if (r < 0) + return r; + + if (ret) + *ret = n; + return 0; +} + _public_ int sd_device_get_action(sd_device *device, sd_device_action_t *ret) { assert_return(device, -EINVAL); @@ -1702,18 +1706,13 @@ _public_ int sd_device_get_device_id(sd_device *device, const char **ret) { if (!device->device_id) { _cleanup_free_ char *id = NULL; - const char *subsystem; dev_t devnum; int ifindex, r; - r = sd_device_get_subsystem(device, &subsystem); - if (r < 0) - return r; - if (sd_device_get_devnum(device, &devnum) >= 0) { /* use dev_t — b259:131072, c254:0 */ if (asprintf(&id, "%c" DEVNUM_FORMAT_STR, - streq(subsystem, "block") ? 'b' : 'c', + device_in_subsystem(device, "block") ? 'b' : 'c', DEVNUM_FORMAT_VAL(devnum)) < 0) return -ENOMEM; } else if (sd_device_get_ifindex(device, &ifindex) >= 0) { @@ -1731,13 +1730,18 @@ _public_ int sd_device_get_device_id(sd_device *device, const char **ret) { if (r == O_DIRECTORY) return -EINVAL; - if (streq(subsystem, "drivers")) { + if (device_in_subsystem(device, "drivers")) /* the 'drivers' pseudo-subsystem is special, and needs the real * subsystem encoded as well */ - assert(device->driver_subsystem); - id = strjoin("+drivers:", device->driver_subsystem, ":", sysname); - } else + id = strjoin("+drivers:", ASSERT_PTR(device->driver_subsystem), ":", sysname); + else { + const char *subsystem; + r = sd_device_get_subsystem(device, &subsystem); + if (r < 0) + return r; + id = strjoin("+", subsystem, ":", sysname); + } if (!id) return -ENOMEM; } @@ -2326,134 +2330,224 @@ void device_clear_sysattr_cache(sd_device *device) { device->sysattr_values = hashmap_free(device->sysattr_values); } -int device_cache_sysattr_value(sd_device *device, const char *key, char *value) { - _unused_ _cleanup_free_ char *old_value = NULL; - _cleanup_free_ char *new_key = NULL; +typedef struct SysAttrCacheEntry { + char *key; + char *value; + int error; +} SysAttrCacheEntry; + +static SysAttrCacheEntry* sysattr_cache_entry_free(SysAttrCacheEntry *p) { + if (!p) + return NULL; + + free(p->key); + free(p->value); + return mfree(p); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + sysattr_cache_hash_ops, + char, path_hash_func, path_compare, + SysAttrCacheEntry, sysattr_cache_entry_free); + +static int device_cache_sysattr_value_full(sd_device *device, char *key, char *value, int error, bool ignore_uevent) { int r; assert(device); assert(key); + assert(value || error > 0); - /* This takes the reference of the input value. The input value may be NULL. - * This replaces the value if it already exists. */ + /* This takes the reference of the input arguments when cached, hence the caller must not free them + * when a positive return value is returned. The input value may be NULL. This replaces an already + * existing entry. */ - /* First, remove the old cache entry. So, we do not need to clear cache on error. */ - old_value = hashmap_remove2(device->sysattr_values, key, (void **) &new_key); - if (!new_key) { - new_key = strdup(key); - if (!new_key) - return -ENOMEM; - } + if (ignore_uevent && streq(last_path_component(key), "uevent")) + return 0; /* not cached */ - r = hashmap_ensure_put(&device->sysattr_values, &path_hash_ops_free_free, new_key, value); + /* Remove the old cache entry. So, we do not need to clear cache on error. */ + sysattr_cache_entry_free(hashmap_remove(device->sysattr_values, key)); + + /* We use ENOANO as a recognizable error code when we have not read the attribute. */ + if (error == ENOANO) + error = ESTALE; + + _cleanup_free_ SysAttrCacheEntry *entry = new(SysAttrCacheEntry, 1); + if (!entry) + return -ENOMEM; + + *entry = (SysAttrCacheEntry) { + .key = key, + .value = value, + .error = error, + }; + + r = hashmap_ensure_put(&device->sysattr_values, &sysattr_cache_hash_ops, entry->key, entry); if (r < 0) return r; - TAKE_PTR(new_key); - - return 0; + TAKE_PTR(entry); + return 1; /* cached */ } -int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value) { - const char *k = NULL, *value; +int device_cache_sysattr_value(sd_device *device, char *key, char *value, int error) { + return device_cache_sysattr_value_full(device, key, value, error, /* ignore_uevent = */ true); +} + +static int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value) { + SysAttrCacheEntry *entry; assert(device); assert(key); - value = hashmap_get2(device->sysattr_values, key, (void **) &k); - if (!k) - return -ESTALE; /* We have not read the attribute. */ - if (!value) - return -ENOENT; /* We have looked up the attribute before and it did not exist. */ + entry = hashmap_get(device->sysattr_values, key); + if (!entry) + return -ENOANO; /* We have not read the attribute. */ + if (!entry->value) { + /* We have looked up the attribute before and failed. Return the cached error code. */ + assert(entry->error > 0); + return -entry->error; + } if (ret_value) - *ret_value = value; + *ret_value = entry->value; return 0; } -/* We cache all sysattr lookups. If an attribute does not exist, it is stored - * with a NULL value in the cache, otherwise the returned string is stored */ -_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret_value) { - _cleanup_free_ char *value = NULL, *path = NULL; +int device_chase(sd_device *device, const char *path, ChaseFlags flags, char **ret_resolved, int *ret_fd) { + int r; + + assert(device); + assert(path); + const char *syspath; - struct stat statbuf; + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + /* Here, CHASE_PREFIX_ROOT is borrowed. If the flag is set or the specified path is relative, then + * the path will be prefixed with the syspath. Note, we do not pass CHASE_PREFIX_ROOT flag with + * syspath as root to chase(), but we manually concatenate the specified path with syspath before + * calling chase(). Otherwise, we cannot set/get attributes of parent or sibling devices. */ + _cleanup_free_ char *prefixed = NULL; + if (FLAGS_SET(flags, CHASE_PREFIX_ROOT) || !path_is_absolute(path)) { + prefixed = path_join(syspath, path); + if (!prefixed) + return -ENOMEM; + path = prefixed; + flags &= ~CHASE_PREFIX_ROOT; + } + + _cleanup_free_ char *resolved = NULL; + _cleanup_close_ int fd = -EBADF; + r = chase(path, /* root = */ NULL, CHASE_NO_AUTOFS | flags, &resolved, ret_fd ? &fd : NULL); + if (r < 0) + return r; + + /* Refuse to reading/writing files outside of sysfs. */ + if (!path_startswith(resolved, "/sys/")) + return -EINVAL; + + if (ret_resolved) { + /* Always return relative path. */ + r = path_make_relative(syspath, resolved, ret_resolved); + if (r < 0) + return r; + } + + if (ret_fd) + *ret_fd = TAKE_FD(fd); + + return 0; +} + +_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret_value) { + _cleanup_free_ char *resolved = NULL, *value = NULL; + _cleanup_close_ int fd = -EBADF; int r; assert_return(device, -EINVAL); assert_return(sysattr, -EINVAL); - /* look for possibly already cached result */ + /* Look for possibly already cached result. */ r = device_get_cached_sysattr_value(device, sysattr, ret_value); - if (r != -ESTALE) + if (r != -ENOANO) return r; - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; + /* Special cases: read the symlink and return the last component of the value. Some core links return + * only the last element of the target path, these are just values, the paths should not be exposed. */ + if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) { + _cleanup_free_ char *prefixed = NULL; + const char *syspath; - path = path_join(syspath, sysattr); - if (!path) - return -ENOMEM; - - if (lstat(path, &statbuf) < 0) { - int k; - - r = -errno; - - /* remember that we could not access the sysattr */ - k = device_cache_sysattr_value(device, sysattr, NULL); - if (k < 0) - log_device_debug_errno(device, k, - "sd-device: failed to cache attribute '%s' with NULL, ignoring: %m", - sysattr); - - return r; - } else if (S_ISLNK(statbuf.st_mode)) { - /* Some core links return only the last element of the target path, - * these are just values, the paths should not be exposed. */ - if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) { - r = readlink_value(path, &value); - if (r < 0) - return r; - } else - return -EINVAL; - } else if (S_ISDIR(statbuf.st_mode)) - /* skip directories */ - return -EISDIR; - else if (!(statbuf.st_mode & S_IRUSR)) - /* skip non-readable files */ - return -EPERM; - else { - size_t size; - - /* Read attribute value, Some attributes contain embedded '\0'. So, it is necessary to - * also get the size of the result. See issue #20025. */ - r = read_full_virtual_file(path, &value, &size); + r = sd_device_get_syspath(device, &syspath); if (r < 0) return r; - /* drop trailing newlines */ - while (size > 0 && strchr(NEWLINE, value[--size])) - value[size] = '\0'; + prefixed = path_join(syspath, sysattr); + if (!prefixed) + return -ENOMEM; + + r = readlink_value(prefixed, &value); + if (r != -EINVAL) /* -EINVAL means the path is not a symlink. */ + goto cache_result; } - /* Unfortunately, we need to return 'const char*' instead of 'char*'. Hence, failure in caching - * sysattr value is critical unlike the other places. */ - r = device_cache_sysattr_value(device, sysattr, value); - if (r < 0) { - log_device_debug_errno(device, r, - "sd-device: failed to cache attribute '%s' with '%s'%s: %m", - sysattr, value, ret_value ? "" : ", ignoring"); - if (ret_value) - return r; + r = device_chase(device, sysattr, CHASE_PREFIX_ROOT, &resolved, &fd); + if (r < 0) + goto cache_result; - return 0; + /* Look for cached result again with the resolved path. */ + r = device_get_cached_sysattr_value(device, resolved, ret_value); + if (r != -ENOANO) + return r; + + /* Read attribute value, Some attributes contain embedded '\0'. So, it is necessary to also get the + * size of the result. See issue #20025. */ + size_t size; + r = read_virtual_file_fd(fd, SIZE_MAX, &value, &size); + if (r < 0) + goto cache_result; + + delete_trailing_chars(value, NEWLINE); + r = 0; + +cache_result: + if (r == -ENOMEM) + return r; /* Do not cache -ENOMEM, as the failure may be transient. */ + + if (!resolved) { + /* If we have not or could not chase the path, assume 'sysattr' is normalized. */ + resolved = strdup(sysattr); + if (!resolved) + return RET_GATHER(r, -ENOMEM); } - if (ret_value) + int k = device_cache_sysattr_value_full(device, resolved, value, -r, /* ignore_uevent = */ false); + if (k < 0) { + if (r < 0) + log_device_debug_errno(device, k, + "sd-device: failed to cache error code (%i) in reading attribute '%s', ignoring: %m", + -r, resolved); + else { + /* Unfortunately, we need to return 'const char*' instead of 'char*'. Hence, failure in caching + * sysattr value is critical unlike the other places. */ + log_device_debug_errno(device, k, + "sd-device: failed to cache attribute '%s' with '%s'%s: %m", + resolved, value, ret_value ? "" : ", ignoring"); + if (ret_value) + return k; + } + + return r; + } + assert(k > 0); + + if (ret_value && r >= 0) *ret_value = value; + /* device_cache_sysattr_value_full() takes 'resolved' and 'value' on success. */ + TAKE_PTR(resolved); TAKE_PTR(value); - return 0; + return r; } int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value) { @@ -2527,19 +2621,22 @@ int device_get_sysattr_bool(sd_device *device, const char *sysattr) { return parse_boolean(value); } -static void device_remove_cached_sysattr_value(sd_device *device, const char *_key) { - _cleanup_free_ char *key = NULL; +static int device_remove_cached_sysattr_value(sd_device *device, const char *sysattr) { + int r; assert(device); - assert(_key); + assert(sysattr); - free(hashmap_remove2(device->sysattr_values, _key, (void **) &key)); + _cleanup_free_ char *resolved = NULL; + r = device_chase(device, sysattr, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &resolved, /* ret_fd = */ NULL); + if (r < 0) + return r; + + sysattr_cache_entry_free(hashmap_remove(device->sysattr_values, resolved)); + return 0; } -_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, const char *_value) { - _cleanup_free_ char *value = NULL, *path = NULL; - const char *syspath; - size_t len; +_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, const char *value) { int r; assert_return(device, -EINVAL); @@ -2547,52 +2644,43 @@ _public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, /* Set the attribute and save it in the cache. */ - if (!_value) { + if (!value) /* If input value is NULL, then clear cache and not write anything. */ - device_remove_cached_sysattr_value(device, sysattr); - return 0; - } + return device_remove_cached_sysattr_value(device, sysattr); - r = sd_device_get_syspath(device, &syspath); - if (r < 0) + _cleanup_free_ char *resolved = NULL; + _cleanup_close_ int fd = -EBADF; + r = device_chase(device, sysattr, CHASE_PREFIX_ROOT, &resolved, &fd); + if (r < 0) { + /* On failure, clear cache entry, hopefully, 'sysattr' is normalized. */ + sysattr_cache_entry_free(hashmap_remove(device->sysattr_values, sysattr)); return r; - - path = path_join(syspath, sysattr); - if (!path) - return -ENOMEM; - - len = strlen(_value); - - /* drop trailing newlines */ - while (len > 0 && strchr(NEWLINE, _value[len - 1])) - len--; + } /* value length is limited to 4k */ - if (len > 4096) - return -EINVAL; - - value = strndup(_value, len); - if (!value) + _cleanup_free_ char *copied = strndup(value, 4096); + if (!copied) return -ENOMEM; - r = write_string_file(path, value, WRITE_STRING_FILE_DISABLE_BUFFER | WRITE_STRING_FILE_NOFOLLOW); + /* drop trailing newlines */ + delete_trailing_chars(copied, NEWLINE); + + r = write_string_file_fd(fd, copied, WRITE_STRING_FILE_DISABLE_BUFFER | WRITE_STRING_FILE_AVOID_NEWLINE); if (r < 0) { /* On failure, clear cache entry, as we do not know how it fails. */ - device_remove_cached_sysattr_value(device, sysattr); + sysattr_cache_entry_free(hashmap_remove(device->sysattr_values, resolved)); return r; } - /* Do not cache action string written into uevent file. */ - if (streq(sysattr, "uevent")) - return 0; - - r = device_cache_sysattr_value(device, sysattr, value); + r = device_cache_sysattr_value(device, resolved, copied, 0); if (r < 0) log_device_debug_errno(device, r, - "sd-device: failed to cache attribute '%s' with '%s', ignoring: %m", - sysattr, value); - else - TAKE_PTR(value); + "sd-device: failed to cache written attribute '%s' with '%s', ignoring: %m", + resolved, copied); + else if (r > 0) { + TAKE_PTR(resolved); + TAKE_PTR(copied); + } return 0; } @@ -2605,10 +2693,8 @@ _public_ int sd_device_set_sysattr_valuef(sd_device *device, const char *sysattr assert_return(device, -EINVAL); assert_return(sysattr, -EINVAL); - if (!format) { - device_remove_cached_sysattr_value(device, sysattr); - return 0; - } + if (!format) + return device_remove_cached_sysattr_value(device, sysattr); va_start(ap, format); r = vasprintf(&value, format, ap); @@ -2621,16 +2707,7 @@ _public_ int sd_device_set_sysattr_valuef(sd_device *device, const char *sysattr } _public_ int sd_device_trigger(sd_device *device, sd_device_action_t action) { - const char *s; - - assert_return(device, -EINVAL); - - s = device_action_to_string(action); - if (!s) - return -EINVAL; - - /* This uses the simple no-UUID interface of kernel < 4.13 */ - return sd_device_set_sysattr_value(device, "uevent", s); + return sd_device_trigger_with_uuid(device, action, NULL); } _public_ int sd_device_trigger_with_uuid( @@ -2644,10 +2721,6 @@ _public_ int sd_device_trigger_with_uuid( assert_return(device, -EINVAL); - /* If no one wants to know the UUID, use the simple interface from pre-4.13 times */ - if (!ret_uuid) - return sd_device_trigger(device, action); - s = device_action_to_string(action); if (!s) return -EINVAL; @@ -2662,7 +2735,8 @@ _public_ int sd_device_trigger_with_uuid( if (r < 0) return r; - *ret_uuid = u; + if (ret_uuid) + *ret_uuid = u; return 0; } 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 862455e19c..d7f15882c8 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 @@ -2,12 +2,21 @@ #include +#include "errno-util.h" #include "event-source.h" #include "event-util.h" #include "fd-util.h" #include "log.h" #include "string-util.h" +#define SI_FLAG_FORWARD (INT32_C(1) << 30) +#define SI_FLAG_POSITIVE (INT32_C(1) << 29) + +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + event_source_hash_ops, + void, trivial_hash_func, trivial_compare_func, + sd_event_source, sd_event_source_disable_unref); + int event_reset_time( sd_event *e, sd_event_source **s, @@ -154,19 +163,73 @@ int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handle int event_add_child_pidref( sd_event *e, - sd_event_source **s, + sd_event_source **ret, const PidRef *pid, int options, sd_event_child_handler_t callback, void *userdata) { + int r; + + assert(e); + if (!pidref_is_set(pid)) return -ESRCH; - if (pid->fd >= 0) - return sd_event_add_child_pidfd(e, s, pid->fd, options, callback, userdata); + if (pidref_is_remote(pid)) + return -EREMOTE; - return sd_event_add_child(e, s, pid->pid, options, callback, userdata); + if (pid->fd < 0) + return sd_event_add_child(e, ret, pid->pid, options, callback, userdata); + + _cleanup_close_ int copy_fd = fcntl(pid->fd, F_DUPFD_CLOEXEC, 3); + if (copy_fd < 0) + return -errno; + + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_child_pidfd(e, &s, copy_fd, options, callback, userdata); + if (r < 0) + return r; + + r = sd_event_source_set_child_pidfd_own(s, true); + if (r < 0) + return r; + + TAKE_FD(copy_fd); + + if (ret) + *ret = TAKE_PTR(s); + else { + r = sd_event_source_set_floating(s, true); + if (r < 0) + return r; + } + + return 0; +} + +int event_source_get_child_pidref(sd_event_source *s, PidRef *ret) { + int r; + + assert(s); + assert(ret); + + pid_t pid; + r = sd_event_source_get_child_pid(s, &pid); + if (r < 0) + return r; + + int pidfd = sd_event_source_get_child_pidfd(s); + if (pidfd < 0) + return pidfd; + + /* Note, we don't actually duplicate the fd here, i.e. we do not pass ownership of this PidRef to the caller */ + *ret = (PidRef) { + .pid = pid, + .fd = pidfd, + }; + + return 0; } dual_timestamp* event_dual_timestamp_now(sd_event *e, dual_timestamp *ts) { @@ -177,3 +240,90 @@ dual_timestamp* event_dual_timestamp_now(sd_event *e, dual_timestamp *ts) { assert_se(sd_event_now(e, CLOCK_MONOTONIC, &ts->monotonic) >= 0); return ts; } + +void event_source_unref_many(sd_event_source **array, size_t n) { + FOREACH_ARRAY(v, array, n) + sd_event_source_unref(*v); + + free(array); +} + +static int event_forward_signal_callback(sd_event_source *s, const struct signalfd_siginfo *ssi, void *userdata) { + sd_event_source *child = ASSERT_PTR(userdata); + + assert(ssi); + + siginfo_t si = { + .si_signo = ssi->ssi_signo, + /* We include some extra information to indicate the signal was forwarded and originally a positive + * value since we can only set negative values ourselves as positive values are prohibited by the + * kernel. */ + .si_code = (ssi->ssi_code & (SI_FLAG_FORWARD|SI_FLAG_POSITIVE)) ? INT_MIN : + (ssi->ssi_code >= 0 ? (-ssi->ssi_code - 1) | SI_FLAG_POSITIVE | SI_FLAG_FORWARD : ssi->ssi_code | SI_FLAG_FORWARD), + .si_errno = ssi->ssi_errno, + }; + + /* The following fields are implemented as macros, hence we cannot use compound initialization for them. */ + si.si_pid = ssi->ssi_pid; + si.si_uid = ssi->ssi_uid; + si.si_int = ssi->ssi_int; + si.si_ptr = UINT64_TO_PTR(ssi->ssi_ptr); + + return sd_event_source_send_child_signal(child, ssi->ssi_signo, &si, /* flags = */ 0); +} + +static void event_forward_signal_destroy(void *userdata) { + sd_event_source *child = ASSERT_PTR(userdata); + sd_event_source_unref(child); +} + +int event_forward_signals( + sd_event *e, + sd_event_source *child, + const int *signals, + size_t n_signals, + sd_event_source ***ret_sources, + size_t *ret_n_sources) { + + sd_event_source **sources = NULL; + size_t n_sources = 0; + int r; + + CLEANUP_ARRAY(sources, n_sources, event_source_unref_many); + + assert(e); + assert(child); + assert(child->type == SOURCE_CHILD); + assert(signals || n_signals == 0); + assert(ret_sources); + assert(ret_n_sources); + + if (n_signals == 0) { + *ret_sources = NULL; + *ret_n_sources = 0; + return 0; + } + + sources = new0(sd_event_source*, n_signals); + if (!sources) + return -ENOMEM; + + FOREACH_ARRAY(sig, signals, n_signals) { + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_signal(e, &s, *sig | SD_EVENT_SIGNAL_PROCMASK, event_forward_signal_callback, child); + if (r < 0) + return r; + + r = sd_event_source_set_destroy_callback(s, event_forward_signal_destroy); + if (r < 0) + return r; + + sd_event_source_ref(child); + sources[n_sources++] = TAKE_PTR(s); + } + + *ret_sources = TAKE_PTR(sources); + *ret_n_sources = n_sources; + + return 0; +} 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 7002ca37da..3d5ee4d331 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 @@ -5,8 +5,11 @@ #include "sd-event.h" +#include "hash-funcs.h" #include "pidref.h" +extern const struct hash_ops event_source_hash_ops; + int event_reset_time( sd_event *e, sd_event_source **s, @@ -37,4 +40,10 @@ int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handle 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); +int event_source_get_child_pidref(sd_event_source *s, PidRef *ret); + dual_timestamp* event_dual_timestamp_now(sd_event *e, dual_timestamp *ts); + +void event_source_unref_many(sd_event_source **array, size_t n); + +int event_forward_signals(sd_event *e, sd_event_source *child, const int *signals, size_t n_signals, sd_event_source ***ret_sources, size_t *ret_n_sources); 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 3d1b6fb228..3f9e21505a 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 @@ -3,6 +3,7 @@ #include #include #include +#include #include "sd-daemon.h" #include "sd-event.h" @@ -24,12 +25,11 @@ #include "memory-util.h" #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 "prioq.h" #include "process-util.h" #include "psi-util.h" #include "set.h" @@ -43,11 +43,10 @@ #define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC) -static bool EVENT_SOURCE_WATCH_PIDFD(sd_event_source *s) { +static bool EVENT_SOURCE_WATCH_PIDFD(const sd_event_source *s) { /* Returns true if this is a PID event source and can be implemented by watching EPOLLIN */ return s && s->type == SOURCE_CHILD && - s->child.pidfd >= 0 && s->child.options == WEXITED; } @@ -432,7 +431,7 @@ _public_ int sd_event_new(sd_event** ret) { if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) { log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 %s 2^63 us will be logged every 5s.", - special_glyph(SPECIAL_GLYPH_ELLIPSIS)); + glyph(GLYPH_ELLIPSIS)); e->profile_delays = true; } @@ -988,7 +987,7 @@ static void source_disconnect(sd_event_source *s) { s->event->n_online_child_sources--; } - (void) hashmap_remove(s->event->child_sources, PID_TO_PTR(s->child.pid)); + assert_se(hashmap_remove(s->event->child_sources, PID_TO_PTR(s->child.pid))); } if (EVENT_SOURCE_WATCH_PIDFD(s)) @@ -1088,12 +1087,11 @@ static sd_event_source* source_free(sd_event_source *s) { /* Eventually the kernel will do this automatically for us, but for now let's emulate this (unreliably) in userspace. */ if (s->child.process_owned) { + assert(s->child.pid > 0); + assert(s->child.pidfd >= 0); if (!s->child.exited) { - 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)); + r = RET_NERRNO(pidfd_send_signal(s->child.pidfd, SIGKILL, NULL, 0)); if (r < 0 && r != -ESRCH) log_debug_errno(r, "Failed to kill process " PID_FMT ", ignoring: %m", s->child.pid); @@ -1103,10 +1101,7 @@ static sd_event_source* source_free(sd_event_source *s) { siginfo_t si = {}; /* Reap the child if we can */ - if (s->child.pidfd >= 0) - (void) waitid(P_PIDFD, s->child.pidfd, &si, WEXITED); - else - (void) waitid(P_PID, s->child.pid, &si, WEXITED); + (void) waitid(P_PIDFD, s->child.pidfd, &si, WEXITED); } } @@ -1896,15 +1891,15 @@ _public_ int sd_event_trim_memory(void) { LOG_MESSAGE("Memory trimming took %s, returned %s to OS.", FORMAT_TIMESPAN(period, 0), FORMAT_BYTES(l)), - "MESSAGE_ID=" SD_MESSAGE_MEMORY_TRIM_STR, - "TRIMMED_BYTES=%zu", l, - "TRIMMED_USEC=" USEC_FMT, period); + LOG_MESSAGE_ID(SD_MESSAGE_MEMORY_TRIM_STR), + LOG_ITEM("TRIMMED_BYTES=%zu", l), + LOG_ITEM("TRIMMED_USEC=" USEC_FMT, period)); #else log_struct(LOG_DEBUG, LOG_MESSAGE("Memory trimming took %s.", FORMAT_TIMESPAN(period, 0)), - "MESSAGE_ID=" SD_MESSAGE_MEMORY_TRIM_STR, - "TRIMMED_USEC=" USEC_FMT, period); + LOG_MESSAGE_ID(SD_MESSAGE_MEMORY_TRIM_STR), + LOG_ITEM("TRIMMED_USEC=" USEC_FMT, period)); #endif return 0; @@ -2730,9 +2725,11 @@ _public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t *ret) { assert_return(s, -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); + if (!s->pending) + return -ENODATA; + *ret = s->io.revents; return 0; } @@ -2999,13 +2996,13 @@ static int event_source_online( case SOURCE_CHILD: if (EVENT_SOURCE_WATCH_PIDFD(s)) { - /* yes, we have pidfd */ + /* yes, we can rely on pidfd */ r = source_child_pidfd_register(s, enabled); if (r < 0) return r; } else { - /* no pidfd, or something other to watch for than WEXITED */ + /* something other to watch for than WEXITED */ r = event_make_signal_data(s->event, SIGCHLD, NULL); if (r < 0) { @@ -3198,9 +3195,6 @@ _public_ int sd_event_source_get_child_pidfd(sd_event_source *s) { assert_return(s->type == SOURCE_CHILD, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - if (s->child.pidfd < 0) - return -EOPNOTSUPP; - return s->child.pidfd; } @@ -3209,51 +3203,26 @@ _public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, cons assert_return(s->type == SOURCE_CHILD, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); assert_return(SIGNAL_VALID(sig), -EINVAL); + assert(s->child.pidfd >= 0); - /* If we already have seen indication the process exited refuse sending a signal early. This way we - * can be sure we don't accidentally kill the wrong process on PID reuse when pidfds are not - * available. */ + /* If we already have seen indication the process exited refuse sending a signal early. */ if (s->child.exited) return -ESRCH; + assert(!s->child.waited); - if (s->child.pidfd >= 0) { - siginfo_t copy; + /* pidfd_send_signal() changes the siginfo_t argument. This is weird, let's hence copy the structure here. */ + siginfo_t copy; + if (si) + copy = *si; - /* pidfd_send_signal() changes the siginfo_t argument. This is weird, let's hence copy the - * structure here */ - if (si) - copy = *si; - - 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 - * this here. */ - if (flags != 0) - return -EOPNOTSUPP; - - if (si) { - /* We use rt_sigqueueinfo() only if siginfo_t is specified. */ - siginfo_t copy = *si; - - if (rt_sigqueueinfo(s->child.pid, sig, ©) < 0) - return -errno; - } else if (kill(s->child.pid, sig) < 0) - return -errno; - - return 0; + return RET_NERRNO(pidfd_send_signal(s->child.pidfd, sig, si ? © : NULL, flags)); } _public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) { assert_return(s, -EINVAL); assert_return(s->type == SOURCE_CHILD, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - - if (s->child.pidfd < 0) - return -EOPNOTSUPP; + assert(s->child.pidfd >= 0); return s->child.pidfd_owned; } @@ -3262,9 +3231,7 @@ _public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) { assert_return(s, -EINVAL); assert_return(s->type == SOURCE_CHILD, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - - if (s->child.pidfd < 0) - return -EOPNOTSUPP; + assert(s->child.pidfd >= 0); s->child.pidfd_owned = own; return 0; @@ -3723,9 +3690,9 @@ static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priori e->need_process_child = false; - /* So, this is ugly. We iteratively invoke waitid() with P_PID + WNOHANG for each PID we wait - * for, instead of using P_ALL. This is because we only want to get child information of very - * specific child processes, and not all of them. We might not have processed the SIGCHLD event + /* So, this is ugly. We iteratively invoke waitid() + WNOHANG with each child process we shall wait for, + * instead of using P_ALL. This is because we only want to get child information of very specific + * child processes, and not all of them. We might not have processed the SIGCHLD event * of a previous invocation and we don't want to maintain a unbounded *per-child* event queue, * hence we really don't want anything flushed out of the kernel's queue that we don't care * about. Since this is O(n) this means that if you have a lot of processes you probably want @@ -3736,6 +3703,7 @@ static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priori HASHMAP_FOREACH(s, e->child_sources) { assert(s->type == SOURCE_CHILD); + assert(s->child.pidfd >= 0); if (s->priority > threshold) continue; @@ -3755,23 +3723,21 @@ static int process_child(sd_event *e, int64_t threshold, int64_t *ret_min_priori continue; zero(s->child.siginfo); - if (waitid(P_PID, s->child.pid, &s->child.siginfo, + if (waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options) < 0) return negative_errno(); if (s->child.siginfo.si_pid != 0) { - bool zombie = IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED); + bool zombie = SIGINFO_CODE_IS_DEAD(s->child.siginfo.si_code); if (zombie) s->child.exited = true; - - if (!zombie && (s->child.options & WEXITED)) { - /* If the child isn't dead then let's immediately remove the state - * change from the queue, since there's no benefit in leaving it - * queued. */ + else if (s->child.options & WEXITED) { + /* If the child isn't dead then let's immediately remove the state change + * from the queue, since there's no benefit in leaving it queued. */ assert(s->child.options & (WSTOPPED|WCONTINUED)); - (void) waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED))); + (void) waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED))); } r = source_set_pending(s, true); @@ -3792,6 +3758,7 @@ static int process_pidfd(sd_event *e, sd_event_source *s, uint32_t revents) { assert(e); assert(s); assert(s->type == SOURCE_CHILD); + assert(s->child.pidfd >= 0); if (s->pending) return 0; @@ -3802,14 +3769,19 @@ static int process_pidfd(sd_event *e, sd_event_source *s, uint32_t revents) { if (!EVENT_SOURCE_WATCH_PIDFD(s)) return 0; + /* Note that pidfd would also generate EPOLLHUP when the process gets reaped. But at this point we + * only permit EPOLLIN, under the assumption that upon EPOLLHUP the child source should already + * be set to pending, and we would have returned early above. */ + assert(!s->child.exited); + zero(s->child.siginfo); - if (waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG | WNOWAIT | s->child.options) < 0) + if (waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG | WNOWAIT | s->child.options) < 0) return -errno; if (s->child.siginfo.si_pid == 0) return 0; - if (IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED)) + if (SIGINFO_CODE_IS_DEAD(s->child.siginfo.si_code)) s->child.exited = true; return source_set_pending(s, true); @@ -4222,15 +4194,13 @@ static int source_dispatch(sd_event_source *s) { break; case SOURCE_CHILD: { - bool zombie; - - zombie = IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED); + bool zombie = SIGINFO_CODE_IS_DEAD(s->child.siginfo.si_code); r = s->child.callback(s, &s->child.siginfo, s->userdata); /* Now, reap the PID for good. */ if (zombie) { - (void) waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|WEXITED); + (void) waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG|WEXITED); s->child.waited = 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 298d21ed29..44b0e1ddda 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,7 @@ #include "hexdecoct.h" #include "id128-util.h" #include "io-util.h" +#include "log.h" #include "namespace-util.h" #include "process-util.h" #include "sha256.h" 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 fc1107b4e8..54fb403a54 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 @@ -2,6 +2,7 @@ #include #include +#include #include #include "sd-id128.h" @@ -14,15 +15,15 @@ #include "id128-util.h" #include "io-util.h" #include "keyring-util.h" +#include "log.h" #include "macro.h" #include "missing_syscall.h" -#include "missing_threads.h" #include "path-util.h" #include "random-util.h" #include "stat-util.h" #include "user-util.h" -_public_ char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]) { +_public_ char *sd_id128_to_string(sd_id128_t id, char s[static SD_ID128_STRING_MAX]) { size_t k = 0; assert_return(s, NULL); @@ -38,7 +39,7 @@ _public_ char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID12 return s; } -_public_ char *sd_id128_to_uuid_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_UUID_STRING_MAX]) { +_public_ char *sd_id128_to_uuid_string(sd_id128_t id, char s[static SD_ID128_UUID_STRING_MAX]) { size_t k = 0; assert_return(s, NULL); @@ -214,8 +215,10 @@ static int get_invocation_from_keyring(sd_id128_t *ret) { key = request_key("user", "invocation_id", NULL, 0); if (key == -1) { - /* Keyring support not available? No invocation key stored? */ - if (IN_SET(errno, ENOSYS, ENOKEY)) + /* Keyring support not available? Keyring access locked down? No invocation key stored? */ + if (ERRNO_IS_NOT_SUPPORTED(errno) || + ERRNO_IS_PRIVILEGE(errno) || + errno == ENOKEY) return -ENXIO; return -errno; diff --git a/src/libnm-systemd-core/src/systemd/_sd-common.h b/src/libnm-systemd-core/src/systemd/_sd-common.h index 5792dd8106..00537eaf16 100644 --- a/src/libnm-systemd-core/src/systemd/_sd-common.h +++ b/src/libnm-systemd-core/src/systemd/_sd-common.h @@ -19,7 +19,7 @@ /* This is a private header; never even think of including this directly! */ -#if defined(__INCLUDE_LEVEL__) && __INCLUDE_LEVEL__ <= 1 && !defined(__COVERITY__) +#if defined(__INCLUDE_LEVEL__) && __INCLUDE_LEVEL__ <= 1 && !defined(__COVERITY__) && !defined(__clang_analyzer__) # error "Do not include _sd-common.h directly; it is a private header." #endif diff --git a/src/libnm-systemd-core/src/systemd/sd-device.h b/src/libnm-systemd-core/src/systemd/sd-device.h index f627ae6dae..8f6141b3df 100644 --- a/src/libnm-systemd-core/src/systemd/sd-device.h +++ b/src/libnm-systemd-core/src/systemd/sd-device.h @@ -23,11 +23,10 @@ #include #include +#include "_sd-common.h" #include "sd-event.h" #include "sd-id128.h" -#include "_sd-common.h" - _SD_BEGIN_DECLARATIONS; typedef struct sd_device sd_device; diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h index d551b4dd90..6054dc4432 100644 --- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h @@ -23,14 +23,13 @@ #include #include +#include "_sd-common.h" #include "sd-device.h" #include "sd-dhcp-duid.h" #include "sd-dhcp6-lease.h" #include "sd-dhcp6-option.h" #include "sd-event.h" -#include "_sd-common.h" - _SD_BEGIN_DECLARATIONS; enum { 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 d6bcceb2a2..5d082b0cbe 100644 --- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-lease.h @@ -23,9 +23,8 @@ #include #include -#include "sd-dhcp6-option.h" - #include "_sd-common.h" +#include "sd-dhcp6-option.h" _SD_BEGIN_DECLARATIONS; diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h index 320124266a..69f3eaa39d 100644 --- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h +++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h @@ -20,9 +20,8 @@ #include #include -#include "sd-dhcp6-protocol.h" - #include "_sd-common.h" +#include "sd-dhcp6-protocol.h" _SD_BEGIN_DECLARATIONS; diff --git a/src/libnm-systemd-core/src/systemd/sd-ndisc.h b/src/libnm-systemd-core/src/systemd/sd-ndisc.h index 85fcf6bc03..a8f8c47b68 100644 --- a/src/libnm-systemd-core/src/systemd/sd-ndisc.h +++ b/src/libnm-systemd-core/src/systemd/sd-ndisc.h @@ -25,14 +25,13 @@ #include #include +#include "_sd-common.h" #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; typedef struct sd_ndisc sd_ndisc; diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.c b/src/libnm-systemd-shared/src/basic/alloc-util.c index 96cf27dc37..1a6b178eb7 100644 --- a/src/libnm-systemd-shared/src/basic/alloc-util.c +++ b/src/libnm-systemd-shared/src/basic/alloc-util.c @@ -6,7 +6,6 @@ #include "alloc-util.h" #include "macro.h" -#include "memory-util.h" void* memdup(const void *p, size_t l) { void *ret; diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.h b/src/libnm-systemd-shared/src/basic/alloc-util.h index ba71298287..764a364a71 100644 --- a/src/libnm-systemd-shared/src/basic/alloc-util.h +++ b/src/libnm-systemd-shared/src/basic/alloc-util.h @@ -7,7 +7,9 @@ #include #include +#include "assert-util.h" #include "macro.h" +#include "memory-util.h" #if HAS_FEATURE_MEMORY_SANITIZER # include @@ -119,15 +121,6 @@ _malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t need, size_t s return malloc(size * need ?: 1); } -#if !HAVE_REALLOCARRAY -_alloc_(2, 3) static inline void *reallocarray(void *p, size_t need, size_t size) { - if (size_multiply_overflow(size, need)) - return NULL; - - return realloc(p, size * need ?: 1); -} -#endif - _alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t need, size_t size) { if (size_multiply_overflow(size, need)) return NULL; @@ -275,5 +268,3 @@ _alloc_(2) static inline void *realloc0(void *p, size_t new_size) { return q; } - -#include "memory-util.h" diff --git a/src/libnm-systemd-shared/src/basic/assert-util.h b/src/libnm-systemd-shared/src/basic/assert-util.h new file mode 100644 index 0000000000..7462e1b222 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/assert-util.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "assert-fundamental.h" +#include "macro.h" + +/* Logging for various assertions */ + +void log_set_assert_return_is_critical(bool b); +bool log_get_assert_return_is_critical(void) _pure_; + +void log_assert_failed_return(const char *text, const char *file, int line, const char *func); + +#define assert_log(expr, message) ((_likely_(expr)) \ + ? (true) \ + : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __func__), false)) + +#define assert_return(expr, r) \ + do { \ + if (!assert_log(expr, #expr)) \ + return (r); \ + } while (false) + +#define assert_return_errno(expr, r, err) \ + do { \ + if (!assert_log(expr, #expr)) { \ + errno = err; \ + return (r); \ + } \ + } while (false) diff --git a/src/libnm-systemd-shared/src/basic/bitfield.h b/src/libnm-systemd-shared/src/basic/bitfield.h index 048e08d753..c6eaaa484f 100644 --- a/src/libnm-systemd-shared/src/basic/bitfield.h +++ b/src/libnm-systemd-shared/src/basic/bitfield.h @@ -7,6 +7,7 @@ #define _INDEX_TO_MASK(type, i, uniq) \ ({ \ int UNIQ_T(_i, uniq) = (i); \ + assert(UNIQ_T(_i, uniq) >= 0); \ assert(UNIQ_T(_i, uniq) < (int)sizeof(type) * 8); \ ((type)1) << UNIQ_T(_i, uniq); \ }) diff --git a/src/libnm-systemd-shared/src/basic/cgroup-util.h b/src/libnm-systemd-shared/src/basic/cgroup-util.h index 77294779fe..949de7f9e2 100644 --- a/src/libnm-systemd-shared/src/basic/cgroup-util.h +++ b/src/libnm-systemd-shared/src/basic/cgroup-util.h @@ -263,8 +263,7 @@ int cg_get_attribute_as_bool(const char *controller, const char *path, const cha int cg_get_owner(const char *path, uid_t *ret_uid); int cg_set_xattr(const char *path, const char *name, const void *value, size_t size, int flags); -int cg_get_xattr(const char *path, const char *name, void *value, size_t size); -int cg_get_xattr_malloc(const char *path, const char *name, char **ret); +int cg_get_xattr_malloc(const char *path, const char *name, char **ret, size_t *ret_size); /* Returns negative on error, and 0 or 1 on success for the bool value */ int cg_get_xattr_bool(const char *path, const char *name); int cg_remove_xattr(const char *path, const char *name); @@ -312,10 +311,6 @@ int cg_mask_supported_subtree(const char *root, CGroupMask *ret); int cg_mask_from_string(const char *s, CGroupMask *ret); int cg_mask_to_string(CGroupMask mask, char **ret); -int cg_kernel_controllers(Set **controllers); - -bool cg_ns_supported(void); -bool cg_freezer_supported(void); bool cg_kill_supported(void); int cg_all_unified(void); @@ -329,9 +324,6 @@ static inline int cg_unified(void) { const char* cgroup_controller_to_string(CGroupController c) _const_; CGroupController cgroup_controller_from_string(const char *s) _pure_; -bool is_cgroup_fs(const struct statfs *s); -bool fd_is_cgroup_fs(int fd); - typedef enum ManagedOOMMode { MANAGED_OOM_AUTO, MANAGED_OOM_KILL, diff --git a/src/libnm-systemd-shared/src/basic/chattr-util.c b/src/libnm-systemd-shared/src/basic/chattr-util.c index 39fdf970a7..794ac6572f 100644 --- a/src/libnm-systemd-shared/src/basic/chattr-util.c +++ b/src/libnm-systemd-shared/src/basic/chattr-util.c @@ -4,13 +4,13 @@ #include #include #include -#include #include "bitfield.h" #include "chattr-util.h" #include "errno-util.h" #include "fd-util.h" #include "fs-util.h" +#include "log.h" #include "macro.h" #include "string-util.h" diff --git a/src/libnm-systemd-shared/src/basic/chattr-util.h b/src/libnm-systemd-shared/src/basic/chattr-util.h index 1fe38e32b1..472054d57e 100644 --- a/src/libnm-systemd-shared/src/basic/chattr-util.h +++ b/src/libnm-systemd-shared/src/basic/chattr-util.h @@ -2,7 +2,6 @@ #pragma once #include -#include #include #include @@ -41,14 +40,14 @@ typedef enum ChattrApplyFlags { } 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_at(int dir_fd, const char *path, unsigned value, unsigned mask) { + return chattr_full(dir_fd, path, value, mask, NULL, 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_fd(int fd, unsigned value, unsigned mask) { + return chattr_full(fd, NULL, value, mask, NULL, 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); +static inline int chattr_path(const char *path, unsigned value, unsigned mask) { + return chattr_full(AT_FDCWD, path, value, mask, NULL, NULL, 0); } int read_attr_fd(int fd, unsigned *ret); diff --git a/src/libnm-systemd-shared/src/basic/devnum-util.h b/src/libnm-systemd-shared/src/basic/devnum-util.h index e109de9913..0efca56780 100644 --- a/src/libnm-systemd-shared/src/basic/devnum-util.h +++ b/src/libnm-systemd-shared/src/basic/devnum-util.h @@ -9,6 +9,9 @@ int parse_devnum(const char *s, dev_t *ret); +#define DEVNUM_MAJOR_MAX ((UINT32_C(1) << 12) - 1U) +#define DEVNUM_MINOR_MAX ((UINT32_C(1) << 20) - 1U) + /* glibc and the Linux kernel have different ideas about the major/minor size. These calls will check whether the * specified major is valid by the Linux kernel's standards, not by glibc's. Linux has 20bits of minor, and 12 bits of * major space. See MINORBITS in linux/kdev_t.h in the kernel sources. (If you wonder why we define _y here, instead of @@ -18,14 +21,14 @@ int parse_devnum(const char *s, dev_t *ret); #define DEVICE_MAJOR_VALID(x) \ ({ \ typeof(x) _x = (x), _y = 0; \ - _x >= _y && _x < (UINT32_C(1) << 12); \ + _x >= _y && _x <= DEVNUM_MAJOR_MAX; \ \ }) #define DEVICE_MINOR_VALID(x) \ ({ \ typeof(x) _x = (x), _y = 0; \ - _x >= _y && _x < (UINT32_C(1) << 20); \ + _x >= _y && _x <= DEVNUM_MINOR_MAX; \ }) int device_path_make_major_minor(mode_t mode, dev_t devnum, char **ret); @@ -54,3 +57,6 @@ static inline char *format_devnum(dev_t d, char buf[static DEVNUM_STR_MAX]) { static inline bool devnum_is_zero(dev_t d) { return major(d) == 0 && minor(d) == 0; } + +#define DEVNUM_TO_PTR(u) ((void*) (uintptr_t) (u)) +#define PTR_TO_DEVNUM(p) ((dev_t) ((uintptr_t) (p))) diff --git a/src/libnm-systemd-shared/src/basic/env-file.c b/src/libnm-systemd-shared/src/basic/env-file.c index 2fff98f8fb..b0faeab4cc 100644 --- a/src/libnm-systemd-shared/src/basic/env-file.c +++ b/src/libnm-systemd-shared/src/basic/env-file.c @@ -7,6 +7,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "log.h" #include "string-util.h" #include "strv.h" #include "tmpfile-util.h" diff --git a/src/libnm-systemd-shared/src/basic/env-util.c b/src/libnm-systemd-shared/src/basic/env-util.c index 99ce1a1842..77a8ccb388 100644 --- a/src/libnm-systemd-shared/src/basic/env-util.c +++ b/src/libnm-systemd-shared/src/basic/env-util.c @@ -11,6 +11,7 @@ #include "errno-util.h" #include "escape.h" #include "extract-word.h" +#include "log.h" #include "macro.h" #include "parse-util.h" #include "path-util.h" @@ -546,7 +547,7 @@ char* strv_env_get_n(char * const *l, const char *name, size_t k, ReplaceEnvFlag return NULL; t = strndupa_safe(name, k); - return getenv(t); + return secure_getenv(t); }; return NULL; @@ -695,7 +696,7 @@ int replace_env_full( _cleanup_strv_free_ char **unset_variables = NULL, **bad_variables = NULL; const char *e, *word = format, *test_value = NULL; /* test_value is initialized to appease gcc */ _cleanup_free_ char *s = NULL; - char ***pu, ***pb, *k; + char ***pu, ***pb; size_t i, len = 0; /* len is initialized to appease gcc */ int nest = 0, r; @@ -717,33 +718,24 @@ int replace_env_full( case CURLY: if (*e == '{') { - k = strnappend(s, word, e-word-1); - if (!k) + if (!strextendn(&s, word, e-word-1)) return -ENOMEM; - free_and_replace(s, k); - word = e-1; state = VARIABLE; nest++; } else if (*e == '$') { - k = strnappend(s, word, e-word); - if (!k) + if (!strextendn(&s, word, e-word)) return -ENOMEM; - free_and_replace(s, k); - word = e+1; state = WORD; } else if (FLAGS_SET(flags, REPLACE_ENV_ALLOW_BRACELESS) && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) { - k = strnappend(s, word, e-word-1); - if (!k) + if (!strextendn(&s, word, e-word-1)) return -ENOMEM; - free_and_replace(s, k); - word = e-1; state = VARIABLE_RAW; @@ -1114,7 +1106,7 @@ int getenv_steal_erase(const char *name, char **ret) { * it from there. Usecase: reading passwords from the env block (which is a bad idea, but useful for * testing, and given that people are likely going to misuse this, be thorough) */ - e = getenv(name); + e = secure_getenv(name); if (!e) { if (ret) *ret = NULL; @@ -1138,25 +1130,6 @@ int getenv_steal_erase(const char *name, char **ret) { return 1; } -int set_full_environment(char **env) { - int r; - - clearenv(); - - STRV_FOREACH(e, env) { - _cleanup_free_ char *k = NULL, *v = NULL; - - r = split_pair(*e, "=", &k, &v); - if (r < 0) - return r; - - if (setenv(k, v, /* overwrite= */ true) < 0) - return -errno; - } - - return 0; -} - int setenvf(const char *name, bool overwrite, const char *valuef, ...) { _cleanup_free_ char *value = NULL; va_list ap; diff --git a/src/libnm-systemd-shared/src/basic/env-util.h b/src/libnm-systemd-shared/src/basic/env-util.h index 203ed65bd1..52771ecc81 100644 --- a/src/libnm-systemd-shared/src/basic/env-util.h +++ b/src/libnm-systemd-shared/src/basic/env-util.h @@ -82,6 +82,4 @@ int getenv_path_list(const char *name, char ***ret_paths); int getenv_steal_erase(const char *name, char **ret); -int set_full_environment(char **env); - int setenvf(const char *name, bool overwrite, const char *valuef, ...) _printf_(3,4); diff --git a/src/libnm-systemd-shared/src/basic/errno-util.h b/src/libnm-systemd-shared/src/basic/errno-util.h index 02572e3bdc..01b4d59e04 100644 --- a/src/libnm-systemd-shared/src/basic/errno-util.h +++ b/src/libnm-systemd-shared/src/basic/errno-util.h @@ -5,6 +5,7 @@ #include #include +#include "assert-util.h" #include "macro.h" /* strerror(3) says that glibc uses a maximum length of 1024 bytes. */ diff --git a/src/libnm-systemd-shared/src/basic/escape.c b/src/libnm-systemd-shared/src/basic/escape.c index e50ae68cc6..e1ded55981 100644 --- a/src/libnm-systemd-shared/src/basic/escape.c +++ b/src/libnm-systemd-shared/src/basic/escape.c @@ -80,10 +80,15 @@ char* cescape_length(const char *s, size_t n) { const char *f; char *r, *t; + /* Does C style string escaping. May be reversed with cunescape(). */ + assert(s || n == 0); - /* Does C style string escaping. May be reversed with - * cunescape(). */ + if (n == SIZE_MAX) + n = strlen(s); + + if (n > (SIZE_MAX - 1) / 4) + return NULL; r = new(char, n*4 + 1); if (!r) @@ -97,12 +102,6 @@ char* cescape_length(const char *s, size_t n) { return r; } -char* cescape(const char *s) { - assert(s); - - return cescape_length(s, strlen(s)); -} - int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul) { int r = 1; @@ -449,7 +448,7 @@ char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFl char* octescape(const char *s, size_t len) { char *buf, *t; - /* Escapes all chars in bad, in addition to \ and " chars, in \nnn style escaping. */ + /* Escapes \ and " chars, in \nnn style escaping. */ assert(s || len == 0); @@ -479,13 +478,19 @@ char* octescape(const char *s, size_t len) { return buf; } -char* decescape(const char *s, const char *bad, size_t len) { +char* decescape(const char *s, size_t len, const char *bad) { char *buf, *t; /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */ 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/escape.h b/src/libnm-systemd-shared/src/basic/escape.h index 65caf0dbcf..05c27f688e 100644 --- a/src/libnm-systemd-shared/src/basic/escape.h +++ b/src/libnm-systemd-shared/src/basic/escape.h @@ -8,7 +8,6 @@ #include #include "string-util.h" -#include "missing_type.h" /* What characters are special in the shell? */ /* must be escaped outside and inside double-quotes */ @@ -41,9 +40,11 @@ typedef enum ShellEscapeFlags { SHELL_ESCAPE_EMPTY = 1 << 2, /* Format empty arguments as "". */ } ShellEscapeFlags; -char* cescape(const char *s); -char* cescape_length(const char *s, size_t n); int cescape_char(char c, char *buf); +char* cescape_length(const char *s, size_t n) _nonnull_if_nonzero_(1, 2); +static inline char* cescape(const char *s) { + return cescape_length(s, SIZE_MAX); +} int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul); @@ -65,7 +66,7 @@ static inline char* xescape(const char *s, const char *bad) { return xescape_full(s, bad, SIZE_MAX, 0); } char* octescape(const char *s, size_t len); -char* decescape(const char *s, const char *bad, size_t len); +char* decescape(const char *s, size_t len, const char *bad) _nonnull_if_nonzero_(1, 2); char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags); char* shell_escape(const char *s, const char *bad); 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 11336572cd..7e85de92fb 100644 --- a/src/libnm-systemd-shared/src/basic/ether-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.c @@ -8,6 +8,7 @@ #include "ether-addr-util.h" #include "hexdecoct.h" +#include "log.h" #include "macro.h" #include "string-util.h" @@ -85,22 +86,6 @@ char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR return buffer; } -int ether_addr_to_string_alloc(const struct ether_addr *addr, char **ret) { - char *buf; - - assert(addr); - assert(ret); - - buf = new(char, ETHER_ADDR_TO_STRING_MAX); - if (!buf) - return -ENOMEM; - - ether_addr_to_string(addr, buf); - - *ret = buf; - return 0; -} - int ether_addr_compare(const struct ether_addr *a, const struct ether_addr *b) { return memcmp(a, b, ETH_ALEN); } 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 8ebf9c031d..168c6500dc 100644 --- a/src/libnm-systemd-shared/src/basic/ether-addr-util.h +++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.h @@ -72,7 +72,6 @@ extern const struct hash_ops hw_addr_hash_ops_free; #define ETHER_ADDR_TO_STRING_MAX (3*6) char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]); -int ether_addr_to_string_alloc(const struct ether_addr *addr, char **ret); /* Use only as function argument, never stand-alone! */ #define ETHER_ADDR_TO_STR(addr) ether_addr_to_string((addr), (char[ETHER_ADDR_TO_STRING_MAX]){}) diff --git a/src/libnm-systemd-shared/src/basic/fd-util.c b/src/libnm-systemd-shared/src/basic/fd-util.c index be22d6a04f..55b274a25e 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.c +++ b/src/libnm-systemd-shared/src/basic/fd-util.c @@ -2,9 +2,7 @@ #include #include -#if WANT_LINUX_FS_H -#include -#endif +#include #include #include #include @@ -17,6 +15,7 @@ #include "fileio.h" #include "fs-util.h" #include "io-util.h" +#include "log.h" #include "macro.h" #include "missing_fcntl.h" #include "missing_fs.h" @@ -29,7 +28,6 @@ #include "sort-util.h" #include "stat-util.h" #include "stdio-util.h" -#include "tmpfile-util.h" /* The maximum number of iterations in the loop to close descriptors in the fallback case * when /proc/self/fd/ is inaccessible. */ @@ -1003,13 +1001,13 @@ int fd_verify_safe_flags_full(int fd, int extra_flags) { if (flags < 0) return -errno; - unexpected_flags = flags & ~(O_ACCMODE|O_NOFOLLOW|RAW_O_LARGEFILE|extra_flags); + unexpected_flags = flags & ~(O_ACCMODE_STRICT|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 */ + return flags & (O_ACCMODE_STRICT | extra_flags); /* return the flags variable, but remove the noise */ } int read_nr_open(void) { @@ -1086,30 +1084,27 @@ int path_is_root_at(int dir_fd, const char *path) { } int fds_are_same_mount(int fd1, int fd2) { - STRUCT_NEW_STATX_DEFINE(st1); - STRUCT_NEW_STATX_DEFINE(st2); + struct statx sx1 = {}, sx2 = {}; /* explicitly initialize the struct to make msan silent. */ int r; assert(fd1 >= 0); assert(fd2 >= 0); - r = statx_fallback(fd1, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st1.sx); - if (r < 0) - return r; + if (statx(fd1, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx1) < 0) + return -errno; - r = statx_fallback(fd2, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st2.sx); - if (r < 0) - return r; + if (statx(fd2, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx2) < 0) + return -errno; /* First, compare inode. If these are different, the fd does not point to the root directory "/". */ - if (!statx_inode_same(&st1.sx, &st2.sx)) + if (!statx_inode_same(&sx1, &sx2)) return false; /* Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old * kernel is used. In that case, let's assume that we do not have such spurious mount points in an * early boot stage, and silently skip the following check. */ - if (!FLAGS_SET(st1.nsx.stx_mask, STATX_MNT_ID)) { + if (!FLAGS_SET(sx1.stx_mask, STATX_MNT_ID)) { int mntid; r = path_get_mnt_id_at_fallback(fd1, "", &mntid); @@ -1117,11 +1112,11 @@ int fds_are_same_mount(int fd1, int fd2) { return r; assert(mntid >= 0); - st1.nsx.stx_mnt_id = mntid; - st1.nsx.stx_mask |= STATX_MNT_ID; + sx1.stx_mnt_id = mntid; + sx1.stx_mask |= STATX_MNT_ID; } - if (!FLAGS_SET(st2.nsx.stx_mask, STATX_MNT_ID)) { + if (!FLAGS_SET(sx2.stx_mask, STATX_MNT_ID)) { int mntid; r = path_get_mnt_id_at_fallback(fd2, "", &mntid); @@ -1129,15 +1124,15 @@ int fds_are_same_mount(int fd1, int fd2) { return r; assert(mntid >= 0); - st2.nsx.stx_mnt_id = mntid; - st2.nsx.stx_mask |= STATX_MNT_ID; + sx2.stx_mnt_id = mntid; + sx2.stx_mask |= STATX_MNT_ID; } - return statx_mount_same(&st1.nsx, &st2.nsx); + return statx_mount_same(&sx1, &sx2); } const char* accmode_to_string(int flags) { - switch (flags & O_ACCMODE) { + switch (flags & O_ACCMODE_STRICT) { case O_RDONLY: return "ro"; case O_WRONLY: diff --git a/src/libnm-systemd-shared/src/basic/fd-util.h b/src/libnm-systemd-shared/src/basic/fd-util.h index 93b254c680..59c43e8140 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 "memory-util.h" #include "missing_fcntl.h" #include "stdio-util.h" diff --git a/src/libnm-systemd-shared/src/basic/fileio.c b/src/libnm-systemd-shared/src/basic/fileio.c index 9f2bbc1323..c5eedc312b 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.c +++ b/src/libnm-systemd-shared/src/basic/fileio.c @@ -244,17 +244,13 @@ static int write_string_file_atomic_at( } r = fopen_temporary_at(dir_fd, fn, &f, &p); + if (call_label_ops_post) + /* If fopen_temporary_at() failed in the above, propagate the error code, and ignore failures + * in label_ops_post(). */ + RET_GATHER(r, label_ops_post(f ? fileno(f) : dir_fd, f ? NULL : fn, /* created= */ !!f)); if (r < 0) goto fail; - if (call_label_ops_post) { - call_label_ops_post = false; - - 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; @@ -277,9 +273,6 @@ static int write_string_file_atomic_at( return 0; fail: - 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; @@ -293,24 +286,27 @@ int write_string_file_full( const struct timespec *ts, const char *label_fn) { - bool call_label_ops_post = false, made_file = false; + bool made_file = false; _cleanup_fclose_ FILE *f = NULL; _cleanup_close_ int fd = -EBADF; int r; - assert(fn); + assert(dir_fd == AT_FDCWD || dir_fd >= 0); assert(line); /* We don't know how to verify whether the file contents was already on-disk. */ assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC))); if (flags & WRITE_STRING_FILE_MKDIR_0755) { + assert(fn); + r = mkdirat_parents(dir_fd, fn, 0755); if (r < 0) return r; } if (flags & WRITE_STRING_FILE_ATOMIC) { + assert(fn); assert(flags & WRITE_STRING_FILE_CREATE); r = write_string_file_atomic_at(dir_fd, fn, line, flags, ts); @@ -320,37 +316,39 @@ int write_string_file_full( return r; } - 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_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 = fd; + if (isempty(fn)) + r = fd = fd_reopen( + ASSERT_FD(dir_fd), O_CLOEXEC | O_NOCTTY | + (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) | + (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY)); + else { + mode_t mode = write_string_file_flags_to_mode(flags); + bool call_label_ops_post = false; + + 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; + } + + r = 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 (call_label_ops_post) + /* If openat_report_new() failed in the above, propagate the error code, and ignore + * failures in label_ops_post(). */ + RET_GATHER(r, label_ops_post(fd >= 0 ? fd : dir_fd, fd >= 0 ? NULL : fn, made_file)); + } + if (r < 0) 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) @@ -366,9 +364,6 @@ int write_string_file_full( 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); @@ -380,7 +375,7 @@ fail: /* 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) + if (verify_file_at(dir_fd, fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) || (flags & WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE)) > 0) return 0; return r; @@ -442,7 +437,6 @@ int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_ext size_t l, k; int r; - assert(fn); assert(blob); l = strlen(blob); @@ -454,7 +448,7 @@ int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_ext if (!buf) return -ENOMEM; - r = fopen_unlocked_at(dir_fd, fn, "re", 0, &f); + r = fopen_unlocked_at(dir_fd, strempty(fn), "re", 0, &f); if (r < 0) return r; @@ -474,7 +468,13 @@ int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_ext return 1; } -int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *ret_size) { +int read_virtual_file_at( + int dir_fd, + const char *filename, + size_t max_size, + char **ret_contents, + size_t *ret_size) { + _cleanup_free_ char *buf = NULL; size_t n, size; int n_retries; @@ -491,11 +491,23 @@ int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *r * max_size specifies a limit on the bytes read. If max_size is SIZE_MAX, the full file is read. If * the full file is too large to read, an error is returned. For other values of max_size, *partial * contents* may be returned. (Though the read is still done using one syscall.) Returns 0 on - * partial success, 1 if untruncated contents were read. */ + * partial success, 1 if untruncated contents were read. + * + * Rule: for kernfs files using "seq_file" → use regular read_full_file_at() + * for kernfs files using "raw" → use read_virtual_file_at() + */ - assert(fd >= 0); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(max_size <= READ_VIRTUAL_BYTES_MAX || max_size == SIZE_MAX); + _cleanup_close_ int fd = -EBADF; + if (isempty(filename)) + fd = fd_reopen(ASSERT_FD(dir_fd), O_RDONLY | O_NOCTTY | O_CLOEXEC); + else + fd = RET_NERRNO(openat(dir_fd, filename, O_RDONLY | O_NOCTTY | O_CLOEXEC)); + if (fd < 0) + return fd; + /* Limit the number of attempts to read the number of bytes returned by fstat(). */ n_retries = 3; @@ -619,31 +631,6 @@ int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *r return !truncated; } -int read_virtual_file_at( - int dir_fd, - const char *filename, - size_t max_size, - char **ret_contents, - size_t *ret_size) { - - _cleanup_close_ int fd = -EBADF; - - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - - if (!filename) { - if (dir_fd == AT_FDCWD) - return -EBADF; - - return read_virtual_file_fd(dir_fd, max_size, ret_contents, ret_size); - } - - fd = openat(dir_fd, filename, O_RDONLY | O_NOCTTY | O_CLOEXEC); - if (fd < 0) - return -errno; - - return read_virtual_file_fd(fd, max_size, ret_contents, ret_size); -} - int read_full_stream_full( FILE *f, const char *filename, @@ -723,7 +710,7 @@ int read_full_stream_full( size_t k; /* If we shall fail when reading overly large data, then read exactly one byte more than the - * specified size at max, since that'll tell us if there's anymore data beyond the limit*/ + * specified size at max, since that'll tell us if there's anymore data beyond the limit. */ if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && n_next > size) n_next = size + 1; @@ -902,74 +889,46 @@ int script_get_shebang_interpreter(const char *path, char **ret) { return 0; } -/** - * Retrieve one field from a file like /proc/self/status. pattern - * should not include whitespace or the delimiter (':'). pattern matches only - * the beginning of a line. Whitespace before ':' is skipped. Whitespace and - * zeros after the ':' will be skipped. field must be freed afterwards. - * terminator specifies the terminating characters of the field value (not - * included in the value). - */ -int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field) { - _cleanup_free_ char *status = NULL; - char *t, *f; +int get_proc_field(const char *path, const char *key, char **ret) { + _cleanup_fclose_ FILE *f = NULL; int r; - assert(terminator); - assert(filename); - assert(pattern); - assert(field); + /* Retrieve one field from a file like /proc/self/status. "key" matches the beginning of the line + * and should not include whitespace or the delimiter (':'). + * Whitespaces after the ':' will be skipped. Only the first element is returned + * (i.e. for /proc/meminfo line "MemTotal: 1024 kB" -> return "1024"). */ - r = read_full_virtual_file(filename, &status, NULL); + assert(path); + assert(key); + + r = fopen_unlocked(path, "re", &f); + if (r == -ENOENT && proc_mounted() == 0) + return -ENOSYS; if (r < 0) return r; - t = status; + for (;;) { + _cleanup_free_ char *line = NULL; - do { - bool pattern_ok; + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) + return -ENODATA; - do { - t = strstr(t, pattern); - if (!t) - return -ENOENT; + char *l = startswith(line, key); + if (l && *l == ':') { + if (ret) { + char *s = strdupcspn(skip_leading_chars(l + 1, " \t"), WHITESPACE); + if (!s) + return -ENOMEM; - /* Check that pattern occurs in beginning of line. */ - pattern_ok = (t == status || t[-1] == '\n'); + *ret = s; + } - t += strlen(pattern); - - } while (!pattern_ok); - - t += strspn(t, " \t"); - if (!*t) - return -ENOENT; - - } while (*t != ':'); - - t++; - - if (*t) { - t += strspn(t, " \t"); - - /* Also skip zeros, because when this is used for - * capabilities, we don't want the zeros. This way the - * same capability set always maps to the same string, - * irrespective of the total capability set size. For - * other numbers it shouldn't matter. */ - t += strspn(t, "0"); - /* Back off one char if there's nothing but whitespace - and zeros */ - if (!*t || isspace(*t)) - t--; + return 0; + } } - - f = strdupcspn(t, terminator); - if (!f) - return -ENOMEM; - - *field = f; - return 0; } DIR* xopendirat(int dir_fd, const char *name, int flags) { diff --git a/src/libnm-systemd-shared/src/basic/fileio.h b/src/libnm-systemd-shared/src/basic/fileio.h index bd053050e1..eff9880292 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.h +++ b/src/libnm-systemd-shared/src/basic/fileio.h @@ -56,6 +56,9 @@ int write_string_file_full(int dir_fd, const char *fn, const char *line, WriteSt 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_fd(int dir_fd, const char *line, WriteStringFileFlags flags) { + return write_string_file_at(dir_fd, NULL, line, flags); +} static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) { return write_string_file_at(AT_FDCWD, fn, line, flags); } @@ -75,8 +78,10 @@ static inline int read_full_file(const char *filename, char **ret_contents, size return read_full_file_full(AT_FDCWD, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size); } -int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *ret_size); int read_virtual_file_at(int dir_fd, const char *filename, size_t max_size, char **ret_contents, size_t *ret_size); +static inline int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *ret_size) { + return read_virtual_file_at(fd, NULL, max_size, ret_contents, ret_size); +} static inline int read_virtual_file(const char *filename, size_t max_size, char **ret_contents, size_t *ret_size) { return read_virtual_file_at(AT_FDCWD, filename, max_size, ret_contents, ret_size); } @@ -90,13 +95,10 @@ static inline int read_full_stream(FILE *f, char **ret_contents, size_t *ret_siz } int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl); -static inline int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { - return verify_file_at(AT_FDCWD, fn, blob, accept_extra_nl); -} 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); +int get_proc_field(const char *path, const char *key, char **ret); DIR* xopendirat(int dir_fd, const char *name, int flags); diff --git a/src/libnm-systemd-shared/src/basic/format-ifname.c b/src/libnm-systemd-shared/src/basic/format-ifname.c index ce4933c57d..8331d20288 100644 --- a/src/libnm-systemd-shared/src/basic/format-ifname.c +++ b/src/libnm-systemd-shared/src/basic/format-ifname.c @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "format-ifname.h" +#include "log.h" +#include "stdio-util.h" #include "string-util.h" assert_cc(STRLEN("%") + DECIMAL_STR_MAX(int) <= 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 09cb716e7d..079b32c04d 100644 --- a/src/libnm-systemd-shared/src/basic/format-util.c +++ b/src/libnm-systemd-shared/src/basic/format-util.c @@ -62,5 +62,4 @@ char* format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) { finish: buf[l-1] = 0; return buf; - } diff --git a/src/libnm-systemd-shared/src/basic/fs-util.c b/src/libnm-systemd-shared/src/basic/fs-util.c index 3b3fa1811e..3b619a15ac 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.c +++ b/src/libnm-systemd-shared/src/basic/fs-util.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include +#include #include #include #include -#include -#include #include #include "alloc-util.h" @@ -77,6 +77,11 @@ int rmdir_parents(const char *path, const char *stop) { int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { int r; + assert(olddirfd >= 0 || olddirfd == AT_FDCWD); + assert(oldpath); + assert(newdirfd >= 0 || newdirfd == AT_FDCWD); + assert(newpath); + /* Try the ideal approach first */ if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) >= 0) return 0; @@ -783,7 +788,7 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) { } } - /* Don't dallocate if there's nothing to deallocate or if the file is linked elsewhere */ + /* Don't deallocate if there's nothing to deallocate or if the file is linked elsewhere */ if (st.st_blocks == 0 || st.st_nlink > 0) return 0; @@ -1031,7 +1036,7 @@ int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_ if (flags & ~(O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_EXCL|O_NOATIME|O_NOFOLLOW|O_PATH)) return -EINVAL; - if ((flags & O_ACCMODE) != O_RDONLY) + if ((flags & O_ACCMODE_STRICT) != O_RDONLY) return -EINVAL; /* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following @@ -1254,7 +1259,7 @@ int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_ } if (FLAGS_SET(xopen_flags, XO_NOCOW)) { - r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL); + r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL); if (r < 0 && !ERRNO_IS_NOT_SUPPORTED(r)) goto error; } diff --git a/src/libnm-systemd-shared/src/basic/fs-util.h b/src/libnm-systemd-shared/src/basic/fs-util.h index eb031a0ccd..b2c0107990 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.h +++ b/src/libnm-systemd-shared/src/basic/fs-util.h @@ -172,3 +172,12 @@ static inline int at_flags_normalize_nofollow(int flags) { flags |= AT_SYMLINK_NOFOLLOW; return flags; } + +static inline int at_flags_normalize_follow(int flags) { + if (FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW)) { + assert(!FLAGS_SET(flags, AT_SYMLINK_FOLLOW)); + flags &= ~AT_SYMLINK_NOFOLLOW; + } else + flags |= AT_SYMLINK_FOLLOW; + 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 64f6685cb4..330872bad3 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* glyph_full(Glyph 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 @@ -32,133 +32,139 @@ const char* special_glyph_full(SpecialGlyph code, bool force_utf) { * http://git.altlinux.org/people/legion/packages/kbd.git?p=kbd.git;a=blob;f=data/consolefonts/README.eurlatgr */ - static const char* const draw_table[2][_SPECIAL_GLYPH_MAX] = { + static const char* const draw_table[2][_GLYPH_MAX] = { /* ASCII fallback */ [false] = { - [SPECIAL_GLYPH_TREE_VERTICAL] = "| ", - [SPECIAL_GLYPH_TREE_BRANCH] = "|-", - [SPECIAL_GLYPH_TREE_RIGHT] = "`-", - [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] = "*", - [SPECIAL_GLYPH_MULTIPLICATION_SIGN] = "x", - [SPECIAL_GLYPH_CIRCLE_ARROW] = "*", - [SPECIAL_GLYPH_BULLET] = "*", - [SPECIAL_GLYPH_MU] = "u", - [SPECIAL_GLYPH_CHECK_MARK] = "+", - [SPECIAL_GLYPH_CROSS_MARK] = "-", - [SPECIAL_GLYPH_LIGHT_SHADE] = "-", - [SPECIAL_GLYPH_DARK_SHADE] = "X", - [SPECIAL_GLYPH_FULL_BLOCK] = "#", - [SPECIAL_GLYPH_SIGMA] = "S", - [SPECIAL_GLYPH_ARROW_UP] = "^", - [SPECIAL_GLYPH_ARROW_DOWN] = "v", - [SPECIAL_GLYPH_ARROW_LEFT] = "<-", - [SPECIAL_GLYPH_ARROW_RIGHT] = "->", - [SPECIAL_GLYPH_ELLIPSIS] = "...", - [SPECIAL_GLYPH_EXTERNAL_LINK] = "[LNK]", - [SPECIAL_GLYPH_ECSTATIC_SMILEY] = ":-]", - [SPECIAL_GLYPH_HAPPY_SMILEY] = ":-}", - [SPECIAL_GLYPH_SLIGHTLY_HAPPY_SMILEY] = ":-)", - [SPECIAL_GLYPH_NEUTRAL_SMILEY] = ":-|", - [SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY] = ":-(", - [SPECIAL_GLYPH_UNHAPPY_SMILEY] = ":-{", - [SPECIAL_GLYPH_DEPRESSED_SMILEY] = ":-[", - [SPECIAL_GLYPH_LOCK_AND_KEY] = "o-,", - [SPECIAL_GLYPH_TOUCH] = "O=", /* Yeah, not very convincing, can you do it better? */ - [SPECIAL_GLYPH_RECYCLING] = "~", - [SPECIAL_GLYPH_DOWNLOAD] = "\\", - [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] = "@", + [GLYPH_SPACE] = " ", + [GLYPH_TREE_VERTICAL] = "| ", + [GLYPH_TREE_BRANCH] = "|-", + [GLYPH_TREE_RIGHT] = "`-", + [GLYPH_TREE_SPACE] = " ", + [GLYPH_TREE_TOP] = ",-", + [GLYPH_VERTICAL_DOTTED] = ":", + [GLYPH_HORIZONTAL_DOTTED] = "-", + [GLYPH_HORIZONTAL_FAT] = "=", + [GLYPH_TRIANGULAR_BULLET] = ">", + [GLYPH_BLACK_CIRCLE] = "*", + [GLYPH_WHITE_CIRCLE] = "*", + [GLYPH_MULTIPLICATION_SIGN] = "x", + [GLYPH_CIRCLE_ARROW] = "*", + [GLYPH_BULLET] = "*", + [GLYPH_MU] = "u", + [GLYPH_CHECK_MARK] = "+", + [GLYPH_CROSS_MARK] = "-", + [GLYPH_LIGHT_SHADE] = "-", + [GLYPH_DARK_SHADE] = "X", + [GLYPH_FULL_BLOCK] = "#", + [GLYPH_SIGMA] = "S", + [GLYPH_ARROW_UP] = "^", + [GLYPH_ARROW_DOWN] = "v", + [GLYPH_ARROW_LEFT] = "<-", + [GLYPH_ARROW_RIGHT] = "->", + [GLYPH_ELLIPSIS] = "...", + [GLYPH_EXTERNAL_LINK] = "[LNK]", + [GLYPH_ECSTATIC_SMILEY] = ":-]", + [GLYPH_HAPPY_SMILEY] = ":-}", + [GLYPH_SLIGHTLY_HAPPY_SMILEY] = ":-)", + [GLYPH_NEUTRAL_SMILEY] = ":-|", + [GLYPH_SLIGHTLY_UNHAPPY_SMILEY] = ":-(", + [GLYPH_UNHAPPY_SMILEY] = ":-{", + [GLYPH_DEPRESSED_SMILEY] = ":-[", + [GLYPH_LOCK_AND_KEY] = "o-,", + [GLYPH_TOUCH] = "O=", /* Yeah, not very convincing, can you do it better? */ + [GLYPH_RECYCLING] = "~", + [GLYPH_DOWNLOAD] = "\\", + [GLYPH_SPARKLES] = "*", + [GLYPH_LOW_BATTERY] = "!", + [GLYPH_WARNING_SIGN] = "!", + [GLYPH_RED_CIRCLE] = "o", + [GLYPH_YELLOW_CIRCLE] = "o", + [GLYPH_BLUE_CIRCLE] = "o", + [GLYPH_GREEN_CIRCLE] = "o", + [GLYPH_SUPERHERO] = "S", + [GLYPH_IDCARD] = "@", + [GLYPH_HOME] = "^", }, /* UTF-8 */ [true] = { + /* This exists to allow more consistent handling of optional whitespace */ + [GLYPH_SPACE] = " ", + /* The following are multiple glyphs in both ASCII and in UNICODE */ - [SPECIAL_GLYPH_TREE_VERTICAL] = u8"│ ", - [SPECIAL_GLYPH_TREE_BRANCH] = u8"├─", - [SPECIAL_GLYPH_TREE_RIGHT] = u8"└─", - [SPECIAL_GLYPH_TREE_SPACE] = u8" ", - [SPECIAL_GLYPH_TREE_TOP] = u8"┌─", + [GLYPH_TREE_VERTICAL] = UTF8("│ "), + [GLYPH_TREE_BRANCH] = UTF8("├─"), + [GLYPH_TREE_RIGHT] = UTF8("└─"), + [GLYPH_TREE_SPACE] = UTF8(" "), + [GLYPH_TREE_TOP] = UTF8("┌─"), /* 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"○", - [SPECIAL_GLYPH_MULTIPLICATION_SIGN] = u8"×", - [SPECIAL_GLYPH_CIRCLE_ARROW] = u8"↻", - [SPECIAL_GLYPH_BULLET] = u8"•", - [SPECIAL_GLYPH_MU] = u8"μ", /* actually called: GREEK SMALL LETTER MU */ - [SPECIAL_GLYPH_CHECK_MARK] = u8"✓", - [SPECIAL_GLYPH_CROSS_MARK] = u8"✗", /* actually called: BALLOT X */ - [SPECIAL_GLYPH_LIGHT_SHADE] = u8"░", - [SPECIAL_GLYPH_DARK_SHADE] = u8"▒", - [SPECIAL_GLYPH_FULL_BLOCK] = u8"█", - [SPECIAL_GLYPH_SIGMA] = u8"Σ", - [SPECIAL_GLYPH_ARROW_UP] = u8"↑", /* actually called: UPWARDS ARROW */ - [SPECIAL_GLYPH_ARROW_DOWN] = u8"↓", /* actually called: DOWNWARDS ARROW */ + [GLYPH_VERTICAL_DOTTED] = UTF8("┆"), + [GLYPH_HORIZONTAL_DOTTED] = UTF8("┄"), + [GLYPH_HORIZONTAL_FAT] = UTF8("━"), + [GLYPH_TRIANGULAR_BULLET] = UTF8("‣"), + [GLYPH_BLACK_CIRCLE] = UTF8("●"), + [GLYPH_WHITE_CIRCLE] = UTF8("○"), + [GLYPH_MULTIPLICATION_SIGN] = UTF8("×"), + [GLYPH_CIRCLE_ARROW] = UTF8("↻"), + [GLYPH_BULLET] = UTF8("•"), + [GLYPH_MU] = UTF8("μ"), /* actually called: GREEK SMALL LETTER MU */ + [GLYPH_CHECK_MARK] = UTF8("✓"), + [GLYPH_CROSS_MARK] = UTF8("✗"), /* actually called: BALLOT X */ + [GLYPH_LIGHT_SHADE] = UTF8("░"), + [GLYPH_DARK_SHADE] = UTF8("▒"), + [GLYPH_FULL_BLOCK] = UTF8("█"), + [GLYPH_SIGMA] = UTF8("Σ"), + [GLYPH_ARROW_UP] = UTF8("↑"), /* actually called: UPWARDS ARROW */ + [GLYPH_ARROW_DOWN] = UTF8("↓"), /* actually called: DOWNWARDS ARROW */ /* Single glyph in Unicode, two in ASCII */ - [SPECIAL_GLYPH_ARROW_LEFT] = u8"←", /* actually called: LEFTWARDS ARROW */ - [SPECIAL_GLYPH_ARROW_RIGHT] = u8"→", /* actually called: RIGHTWARDS ARROW */ + [GLYPH_ARROW_LEFT] = UTF8("←"), /* actually called: LEFTWARDS ARROW */ + [GLYPH_ARROW_RIGHT] = UTF8("→"), /* actually called: RIGHTWARDS ARROW */ /* Single glyph in Unicode, three in ASCII */ - [SPECIAL_GLYPH_ELLIPSIS] = u8"…", /* actually called: HORIZONTAL ELLIPSIS */ + [GLYPH_ELLIPSIS] = UTF8("…"), /* actually called: HORIZONTAL ELLIPSIS */ /* Three glyphs in Unicode, five in ASCII */ - [SPECIAL_GLYPH_EXTERNAL_LINK] = u8"[🡕]", /* actually called: NORTH EAST SANS-SERIF ARROW, enclosed in [] */ + [GLYPH_EXTERNAL_LINK] = UTF8("[🡕]"), /* actually called: NORTH EAST SANS-SERIF ARROW, enclosed in [] */ /* These smileys are a single glyph in Unicode, and three in ASCII */ - [SPECIAL_GLYPH_ECSTATIC_SMILEY] = u8"😇", /* actually called: SMILING FACE WITH HALO */ - [SPECIAL_GLYPH_HAPPY_SMILEY] = u8"😀", /* actually called: GRINNING FACE */ - [SPECIAL_GLYPH_SLIGHTLY_HAPPY_SMILEY] = u8"🙂", /* actually called: SLIGHTLY SMILING FACE */ - [SPECIAL_GLYPH_NEUTRAL_SMILEY] = u8"😐", /* actually called: NEUTRAL FACE */ - [SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY] = u8"🙁", /* actually called: SLIGHTLY FROWNING FACE */ - [SPECIAL_GLYPH_UNHAPPY_SMILEY] = u8"😨", /* actually called: FEARFUL FACE */ - [SPECIAL_GLYPH_DEPRESSED_SMILEY] = u8"🤢", /* actually called: NAUSEATED FACE */ + [GLYPH_ECSTATIC_SMILEY] = UTF8("😇"), /* actually called: SMILING FACE WITH HALO */ + [GLYPH_HAPPY_SMILEY] = UTF8("😀"), /* actually called: GRINNING FACE */ + [GLYPH_SLIGHTLY_HAPPY_SMILEY] = UTF8("🙂"), /* actually called: SLIGHTLY SMILING FACE */ + [GLYPH_NEUTRAL_SMILEY] = UTF8("😐"), /* actually called: NEUTRAL FACE */ + [GLYPH_SLIGHTLY_UNHAPPY_SMILEY] = UTF8("🙁"), /* actually called: SLIGHTLY FROWNING FACE */ + [GLYPH_UNHAPPY_SMILEY] = UTF8("😨"), /* actually called: FEARFUL FACE */ + [GLYPH_DEPRESSED_SMILEY] = UTF8("🤢"), /* actually called: NAUSEATED FACE */ /* This emoji is a single character cell glyph in Unicode, and three in ASCII */ - [SPECIAL_GLYPH_LOCK_AND_KEY] = u8"🔐", /* actually called: CLOSED LOCK WITH KEY */ + [GLYPH_LOCK_AND_KEY] = UTF8("🔐"), /* actually called: CLOSED LOCK WITH KEY */ /* This emoji is a single character cell glyph in Unicode, and two in ASCII */ - [SPECIAL_GLYPH_TOUCH] = u8"👆", /* actually called: BACKHAND INDEX POINTING UP */ + [GLYPH_TOUCH] = UTF8("👆"), /* actually called: BACKHAND INDEX POINTING UP */ /* These four emojis are single character cell glyphs in Unicode and also in ASCII. */ - [SPECIAL_GLYPH_RECYCLING] = u8"♻️", /* actually called: UNIVERSAL RECYCLNG SYMBOL */ - [SPECIAL_GLYPH_DOWNLOAD] = u8"⤵️", /* actually called: RIGHT ARROW CURVING DOWN */ - [SPECIAL_GLYPH_SPARKLES] = u8"✨", - [SPECIAL_GLYPH_LOW_BATTERY] = u8"🪫", - [SPECIAL_GLYPH_WARNING_SIGN] = u8"⚠️", - [SPECIAL_GLYPH_COMPUTER_DISK] = u8"💽", - [SPECIAL_GLYPH_WORLD] = u8"🌍", + [GLYPH_RECYCLING] = UTF8("♻️"), /* actually called: UNIVERSAL RECYCLNG SYMBOL */ + [GLYPH_DOWNLOAD] = UTF8("⤵️"), /* actually called: RIGHT ARROW CURVING DOWN */ + [GLYPH_SPARKLES] = UTF8("✨"), + [GLYPH_LOW_BATTERY] = UTF8("🪫"), + [GLYPH_WARNING_SIGN] = UTF8("⚠️"), + [GLYPH_COMPUTER_DISK] = UTF8("💽"), + [GLYPH_WORLD] = UTF8("🌍"), - [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"🪪", + [GLYPH_RED_CIRCLE] = UTF8("🔴"), + [GLYPH_YELLOW_CIRCLE] = UTF8("🟡"), + [GLYPH_BLUE_CIRCLE] = UTF8("🔵"), + [GLYPH_GREEN_CIRCLE] = UTF8("🟢"), + [GLYPH_SUPERHERO] = UTF8("🦸"), + [GLYPH_IDCARD] = UTF8("🪪"), + [GLYPH_HOME] = UTF8("🏠"), }, }; if (code < 0) return NULL; - assert(code < _SPECIAL_GLYPH_MAX); - return draw_table[force_utf || (code >= _SPECIAL_GLYPH_FIRST_EMOJI ? emoji_enabled() : is_locale_utf8())][code]; + assert(code < _GLYPH_MAX); + return draw_table[force_utf || (code >= _GLYPH_FIRST_EMOJI ? emoji_enabled() : is_locale_utf8())][code]; } diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.h b/src/libnm-systemd-shared/src/basic/glyph-util.h index ca4d4eda71..730f269560 100644 --- a/src/libnm-systemd-shared/src/basic/glyph-util.h +++ b/src/libnm-systemd-shared/src/basic/glyph-util.h @@ -6,73 +6,79 @@ #include "macro.h" -typedef enum SpecialGlyph { - SPECIAL_GLYPH_TREE_VERTICAL, - SPECIAL_GLYPH_TREE_BRANCH, - SPECIAL_GLYPH_TREE_RIGHT, - 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, - SPECIAL_GLYPH_MULTIPLICATION_SIGN, - SPECIAL_GLYPH_CIRCLE_ARROW, - SPECIAL_GLYPH_BULLET, - SPECIAL_GLYPH_MU, - SPECIAL_GLYPH_CHECK_MARK, - SPECIAL_GLYPH_CROSS_MARK, - SPECIAL_GLYPH_LIGHT_SHADE, - SPECIAL_GLYPH_DARK_SHADE, - SPECIAL_GLYPH_FULL_BLOCK, - SPECIAL_GLYPH_SIGMA, - SPECIAL_GLYPH_ARROW_UP, - SPECIAL_GLYPH_ARROW_DOWN, - SPECIAL_GLYPH_ARROW_LEFT, - SPECIAL_GLYPH_ARROW_RIGHT, - SPECIAL_GLYPH_ELLIPSIS, - SPECIAL_GLYPH_EXTERNAL_LINK, - _SPECIAL_GLYPH_FIRST_EMOJI, - SPECIAL_GLYPH_ECSTATIC_SMILEY = _SPECIAL_GLYPH_FIRST_EMOJI, - SPECIAL_GLYPH_HAPPY_SMILEY, - SPECIAL_GLYPH_SLIGHTLY_HAPPY_SMILEY, - SPECIAL_GLYPH_NEUTRAL_SMILEY, - SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY, - SPECIAL_GLYPH_UNHAPPY_SMILEY, - SPECIAL_GLYPH_DEPRESSED_SMILEY, - SPECIAL_GLYPH_LOCK_AND_KEY, - SPECIAL_GLYPH_TOUCH, - SPECIAL_GLYPH_RECYCLING, - SPECIAL_GLYPH_DOWNLOAD, - SPECIAL_GLYPH_SPARKLES, - SPECIAL_GLYPH_LOW_BATTERY, - 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; +typedef enum Glyph { + GLYPH_SPACE, + GLYPH_TREE_VERTICAL, + GLYPH_TREE_BRANCH, + GLYPH_TREE_RIGHT, + GLYPH_TREE_SPACE, + GLYPH_TREE_TOP, + GLYPH_VERTICAL_DOTTED, + GLYPH_HORIZONTAL_DOTTED, + GLYPH_HORIZONTAL_FAT, + GLYPH_TRIANGULAR_BULLET, + GLYPH_BLACK_CIRCLE, + GLYPH_WHITE_CIRCLE, + GLYPH_MULTIPLICATION_SIGN, + GLYPH_CIRCLE_ARROW, + GLYPH_BULLET, + GLYPH_MU, + GLYPH_CHECK_MARK, + GLYPH_CROSS_MARK, + GLYPH_LIGHT_SHADE, + GLYPH_DARK_SHADE, + GLYPH_FULL_BLOCK, + GLYPH_SIGMA, + GLYPH_ARROW_UP, + GLYPH_ARROW_DOWN, + GLYPH_ARROW_LEFT, + GLYPH_ARROW_RIGHT, + GLYPH_ELLIPSIS, + GLYPH_EXTERNAL_LINK, + _GLYPH_FIRST_EMOJI, + GLYPH_ECSTATIC_SMILEY = _GLYPH_FIRST_EMOJI, + GLYPH_HAPPY_SMILEY, + GLYPH_SLIGHTLY_HAPPY_SMILEY, + GLYPH_NEUTRAL_SMILEY, + GLYPH_SLIGHTLY_UNHAPPY_SMILEY, + GLYPH_UNHAPPY_SMILEY, + GLYPH_DEPRESSED_SMILEY, + GLYPH_LOCK_AND_KEY, + GLYPH_TOUCH, + GLYPH_RECYCLING, + GLYPH_DOWNLOAD, + GLYPH_SPARKLES, + GLYPH_LOW_BATTERY, + GLYPH_WARNING_SIGN, + GLYPH_COMPUTER_DISK, + GLYPH_WORLD, + GLYPH_RED_CIRCLE, + GLYPH_YELLOW_CIRCLE, + GLYPH_BLUE_CIRCLE, + GLYPH_GREEN_CIRCLE, + GLYPH_SUPERHERO, + GLYPH_IDCARD, + GLYPH_HOME, + _GLYPH_MAX, + _GLYPH_INVALID = -EINVAL, +} Glyph; bool emoji_enabled(void); -const char* special_glyph_full(SpecialGlyph code, bool force_utf) _const_; +const char* glyph_full(Glyph code, bool force_utf) _const_; -static inline const char* special_glyph(SpecialGlyph code) { - return special_glyph_full(code, false); +static inline const char* glyph(Glyph code) { + return glyph_full(code, false); } -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* optional_glyph(Glyph code) { + return emoji_enabled() ? glyph(code) : ""; } -static inline const char* special_glyph_check_mark_space(bool b) { - return b ? special_glyph(SPECIAL_GLYPH_CHECK_MARK) : " "; +static inline const char* glyph_check_mark(bool b) { + return b ? glyph(GLYPH_CHECK_MARK) : glyph(GLYPH_CROSS_MARK); +} + +static inline const char* glyph_check_mark_space(bool b) { + return b ? glyph(GLYPH_CHECK_MARK) : " "; } diff --git a/src/libnm-systemd-shared/src/basic/hash-funcs.c b/src/libnm-systemd-shared/src/basic/hash-funcs.c index 251ee4f069..b122c300b8 100644 --- a/src/libnm-systemd-shared/src/basic/hash-funcs.c +++ b/src/libnm-systemd-shared/src/basic/hash-funcs.c @@ -10,15 +10,23 @@ void string_hash_func(const char *p, struct siphash *state) { siphash24_compress(p, strlen(p) + 1, state); } -DEFINE_HASH_OPS(string_hash_ops, char, string_hash_func, string_compare_func); -DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(string_hash_ops_free, - char, string_hash_func, string_compare_func, free); -DEFINE_HASH_OPS_FULL(string_hash_ops_free_free, - char, string_hash_func, string_compare_func, free, - void, free); -DEFINE_HASH_OPS_FULL(string_hash_ops_free_strv_free, - char, string_hash_func, string_compare_func, free, - char*, strv_free); +DEFINE_HASH_OPS(string_hash_ops, + char, string_hash_func, string_compare_func); +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + string_hash_ops_free, + char, string_hash_func, string_compare_func, free); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + string_hash_ops_value_free, + char, string_hash_func, string_compare_func, + void, free); +DEFINE_HASH_OPS_FULL( + string_hash_ops_free_free, + char, string_hash_func, string_compare_func, free, + void, free); +DEFINE_HASH_OPS_FULL( + string_hash_ops_free_strv_free, + char, string_hash_func, string_compare_func, free, + char*, strv_free); void path_hash_func(const char *q, struct siphash *state) { bool add_slash = false; @@ -59,12 +67,15 @@ void path_hash_func(const char *q, struct siphash *state) { } } -DEFINE_HASH_OPS(path_hash_ops, char, path_hash_func, path_compare); -DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(path_hash_ops_free, - char, path_hash_func, path_compare, free); -DEFINE_HASH_OPS_FULL(path_hash_ops_free_free, - char, path_hash_func, path_compare, free, - void, free); +DEFINE_HASH_OPS(path_hash_ops, + char, path_hash_func, path_compare); +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + path_hash_ops_free, + char, path_hash_func, path_compare, free); +DEFINE_HASH_OPS_FULL( + path_hash_ops_free_free, + char, path_hash_func, path_compare, free, + void, free); void trivial_hash_func(const void *p, struct siphash *state) { siphash24_compress_typesafe(p, state); @@ -74,23 +85,19 @@ int trivial_compare_func(const void *a, const void *b) { return CMP(a, b); } -const struct hash_ops trivial_hash_ops = { - .hash = trivial_hash_func, - .compare = trivial_compare_func, -}; - -const struct hash_ops trivial_hash_ops_free = { - .hash = trivial_hash_func, - .compare = trivial_compare_func, - .free_key = free, -}; - -const struct hash_ops trivial_hash_ops_free_free = { - .hash = trivial_hash_func, - .compare = trivial_compare_func, - .free_key = free, - .free_value = free, -}; +DEFINE_HASH_OPS(trivial_hash_ops, + void, trivial_hash_func, trivial_compare_func); +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + trivial_hash_ops_free, + void, trivial_hash_func, trivial_compare_func, free); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + trivial_hash_ops_value_free, + void, trivial_hash_func, trivial_compare_func, + void, free); +DEFINE_HASH_OPS_FULL( + trivial_hash_ops_free_free, + void, trivial_hash_func, trivial_compare_func, free, + void, free); void uint64_hash_func(const uint64_t *p, struct siphash *state) { siphash24_compress_typesafe(*p, state); @@ -100,7 +107,12 @@ int uint64_compare_func(const uint64_t *a, const uint64_t *b) { return CMP(*a, *b); } -DEFINE_HASH_OPS(uint64_hash_ops, uint64_t, uint64_hash_func, uint64_compare_func); +DEFINE_HASH_OPS(uint64_hash_ops, + uint64_t, uint64_hash_func, uint64_compare_func); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + uint64_hash_ops_value_free, + uint64_t, uint64_hash_func, uint64_compare_func, + void, free); #if SIZEOF_DEV_T != 8 void devt_hash_func(const dev_t *p, struct siphash *state) { diff --git a/src/libnm-systemd-shared/src/basic/hash-funcs.h b/src/libnm-systemd-shared/src/basic/hash-funcs.h index 3804e94d98..d0736807ba 100644 --- a/src/libnm-systemd-shared/src/basic/hash-funcs.h +++ b/src/libnm-systemd-shared/src/basic/hash-funcs.h @@ -77,6 +77,7 @@ void string_hash_func(const char *p, struct siphash *state); #define string_compare_func strcmp extern const struct hash_ops string_hash_ops; extern const struct hash_ops string_hash_ops_free; +extern const struct hash_ops string_hash_ops_value_free; extern const struct hash_ops string_hash_ops_free_free; extern const struct hash_ops string_hash_ops_free_strv_free; @@ -91,6 +92,7 @@ void trivial_hash_func(const void *p, struct siphash *state); int trivial_compare_func(const void *a, const void *b) _const_; extern const struct hash_ops trivial_hash_ops; extern const struct hash_ops trivial_hash_ops_free; +extern const struct hash_ops trivial_hash_ops_value_free; extern const struct hash_ops trivial_hash_ops_free_free; /* 32-bit values we can always just embed in the pointer itself, but in order to support 32-bit archs we need store 64-bit @@ -98,6 +100,7 @@ extern const struct hash_ops trivial_hash_ops_free_free; void uint64_hash_func(const uint64_t *p, struct siphash *state); int uint64_compare_func(const uint64_t *a, const uint64_t *b) _pure_; extern const struct hash_ops uint64_hash_ops; +extern const struct hash_ops uint64_hash_ops_value_free; /* On some archs dev_t is 32-bit, and on others 64-bit. And sometimes it's 64-bit on 32-bit archs, and sometimes 32-bit on * 64-bit archs. Yuck! */ diff --git a/src/libnm-systemd-shared/src/basic/hashmap.c b/src/libnm-systemd-shared/src/basic/hashmap.c index 951f63ae6d..6bbcd70c07 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.c +++ b/src/libnm-systemd-shared/src/basic/hashmap.c @@ -12,6 +12,7 @@ #include "alloc-util.h" #include "fileio.h" #include "hashmap.h" +#include "log.h" #include "logarithm.h" #include "macro.h" #include "memory-util.h" @@ -912,24 +913,20 @@ static void hashmap_free_no_clear(HashmapBase *h) { free(h); } -HashmapBase* _hashmap_free(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value) { +HashmapBase* _hashmap_free(HashmapBase *h) { if (h) { - _hashmap_clear(h, default_free_key, default_free_value); + _hashmap_clear(h); hashmap_free_no_clear(h); } return NULL; } -void _hashmap_clear(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value) { - free_func_t free_key, free_value; +void _hashmap_clear(HashmapBase *h) { if (!h) return; - free_key = h->hash_ops->free_key ?: default_free_key; - free_value = h->hash_ops->free_value ?: default_free_value; - - if (free_key || free_value) { + if (h->hash_ops->free_key || h->hash_ops->free_value) { /* If destructor calls are defined, let's destroy things defensively: let's take the item out of the * hash table, and only then call the destructor functions. If these destructors then try to unregister @@ -941,11 +938,11 @@ void _hashmap_clear(HashmapBase *h, free_func_t default_free_key, free_func_t de v = _hashmap_first_key_and_value(h, true, &k); - if (free_key) - free_key(k); + if (h->hash_ops->free_key) + h->hash_ops->free_key(k); - if (free_value) - free_value(v); + if (h->hash_ops->free_value) + h->hash_ops->free_value(v); } } @@ -1780,7 +1777,7 @@ HashmapBase* _hashmap_copy(HashmapBase *h HASHMAP_DEBUG_PARAMS) { } if (r < 0) - return _hashmap_free(copy, NULL, NULL); + return _hashmap_free(copy); return copy; } @@ -1805,6 +1802,23 @@ char** _hashmap_get_strv(HashmapBase *h) { return sv; } +char** set_to_strv(Set **s) { + assert(s); + + /* This is similar to set_get_strv(), but invalidates the set on success. */ + + char **v = new(char*, set_size(*s) + 1); + if (!v) + return NULL; + + for (char **p = v; (*p = set_steal_first(*s)); p++) + ; + + assert(set_isempty(*s)); + *s = set_free(*s); + return v; +} + void* ordered_hashmap_next(OrderedHashmap *h, const void *key) { struct ordered_hashmap_entry *e; unsigned hash, idx; diff --git a/src/libnm-systemd-shared/src/basic/hashmap.h b/src/libnm-systemd-shared/src/basic/hashmap.h index 01a4fb3204..8ced7e6a99 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.h +++ b/src/libnm-systemd-shared/src/basic/hashmap.h @@ -88,36 +88,17 @@ OrderedHashmap* _ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DE #define hashmap_new(ops) _hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) #define ordered_hashmap_new(ops) _ordered_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) -#define hashmap_free_and_replace(a, b) \ +#define hashmap_free_and_replace(a, b) \ free_and_replace_full(a, b, hashmap_free) +#define ordered_hashmap_free_and_replace(a, b) \ + free_and_replace_full(a, b, ordered_hashmap_free) -HashmapBase* _hashmap_free(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value); +HashmapBase* _hashmap_free(HashmapBase *h); static inline Hashmap* hashmap_free(Hashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), NULL, NULL); + return (void*) _hashmap_free(HASHMAP_BASE(h)); } static inline OrderedHashmap* ordered_hashmap_free(OrderedHashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), NULL, NULL); -} - -static inline Hashmap* hashmap_free_free(Hashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), NULL, free); -} -static inline OrderedHashmap* ordered_hashmap_free_free(OrderedHashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), NULL, free); -} - -static inline Hashmap* hashmap_free_free_key(Hashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), free, NULL); -} -static inline OrderedHashmap* ordered_hashmap_free_free_key(OrderedHashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), free, NULL); -} - -static inline Hashmap* hashmap_free_free_free(Hashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), free, free); -} -static inline OrderedHashmap* ordered_hashmap_free_free_free(OrderedHashmap *h) { - return (void*) _hashmap_free(HASHMAP_BASE(h), free, free); + return (void*) _hashmap_free(HASHMAP_BASE(h)); } IteratedCache* iterated_cache_free(IteratedCache *cache); @@ -285,33 +266,12 @@ static inline bool ordered_hashmap_iterate(OrderedHashmap *h, Iterator *i, void return _hashmap_iterate(HASHMAP_BASE(h), i, value, key); } -void _hashmap_clear(HashmapBase *h, free_func_t default_free_key, free_func_t default_free_value); +void _hashmap_clear(HashmapBase *h); static inline void hashmap_clear(Hashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), NULL, NULL); + _hashmap_clear(HASHMAP_BASE(h)); } static inline void ordered_hashmap_clear(OrderedHashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), NULL, NULL); -} - -static inline void hashmap_clear_free(Hashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), NULL, free); -} -static inline void ordered_hashmap_clear_free(OrderedHashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), NULL, free); -} - -static inline void hashmap_clear_free_key(Hashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), free, NULL); -} -static inline void ordered_hashmap_clear_free_key(OrderedHashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), free, NULL); -} - -static inline void hashmap_clear_free_free(Hashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), free, free); -} -static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) { - _hashmap_clear(HASHMAP_BASE(h), free, free); + _hashmap_clear(HASHMAP_BASE(h)); } /* @@ -371,27 +331,6 @@ static inline void *ordered_hashmap_first_key(OrderedHashmap *h) { return _hashmap_first_key(HASHMAP_BASE(h), false); } -#define hashmap_clear_with_destructor(h, f) \ - ({ \ - Hashmap *_h = (h); \ - void *_item; \ - while ((_item = hashmap_steal_first(_h))) \ - f(_item); \ - _h; \ - }) -#define hashmap_free_with_destructor(h, f) \ - hashmap_free(hashmap_clear_with_destructor(h, f)) -#define ordered_hashmap_clear_with_destructor(h, f) \ - ({ \ - OrderedHashmap *_h = (h); \ - void *_item; \ - while ((_item = ordered_hashmap_steal_first(_h))) \ - f(_item); \ - _h; \ - }) -#define ordered_hashmap_free_with_destructor(h, f) \ - ordered_hashmap_free(ordered_hashmap_clear_with_destructor(h, f)) - /* no hashmap_next */ void* ordered_hashmap_next(OrderedHashmap *h, const void *key); @@ -459,20 +398,10 @@ static inline int ordered_hashmap_dump_keys_sorted(OrderedHashmap *h, void ***re _ORDERED_HASHMAP_FOREACH_KEY(e, k, h, UNIQ_T(i, UNIQ)) DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_key); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free); DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_key); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free); #define _cleanup_hashmap_free_ _cleanup_(hashmap_freep) -#define _cleanup_hashmap_free_free_ _cleanup_(hashmap_free_freep) -#define _cleanup_hashmap_free_free_free_ _cleanup_(hashmap_free_free_freep) #define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep) -#define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep) -#define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep) DEFINE_TRIVIAL_CLEANUP_FUNC(IteratedCache*, iterated_cache_free); diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.h b/src/libnm-systemd-shared/src/basic/hexdecoct.h index d160ca28c9..b456200e1b 100644 --- a/src/libnm-systemd-shared/src/basic/hexdecoct.h +++ b/src/libnm-systemd-shared/src/basic/hexdecoct.h @@ -17,8 +17,8 @@ int undecchar(char c) _const_; char hexchar(int x) _const_; int unhexchar(char c) _const_; -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); +char* hexmem(const void *p, size_t l) _nonnull_if_nonzero_(1, 2); +int unhexmem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size) _nonnull_if_nonzero_(1, 2); 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,10 +30,10 @@ 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); -int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len); +char* base32hexmem(const void *p, size_t l, bool padding) _nonnull_if_nonzero_(1, 2); +int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len) _nonnull_if_nonzero_(1, 2); -ssize_t base64mem_full(const void *p, size_t l, size_t line_break, char **ret); +ssize_t base64mem_full(const void *p, size_t l, size_t line_break, char **ret) _nonnull_if_nonzero_(1, 2); static inline ssize_t base64mem(const void *p, size_t l, char **ret) { return base64mem_full(p, l, SIZE_MAX, ret); } @@ -45,9 +45,9 @@ ssize_t base64_append( size_t l, size_t margin, size_t width); -int unbase64mem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size); +int unbase64mem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size) _nonnull_if_nonzero_(1, 2); static inline int unbase64mem(const char *p, void **ret_data, size_t *ret_size) { return unbase64mem_full(p, SIZE_MAX, false, ret_data, ret_size); } -void hexdump(FILE *f, const void *p, size_t s); +void hexdump(FILE *f, const void *p, size_t s) _nonnull_if_nonzero_(2, 3); diff --git a/src/libnm-systemd-shared/src/basic/hostname-util.c b/src/libnm-systemd-shared/src/basic/hostname-util.c index e743033b1e..5507303055 100644 --- a/src/libnm-systemd-shared/src/basic/hostname-util.c +++ b/src/libnm-systemd-shared/src/basic/hostname-util.c @@ -10,17 +10,21 @@ #include "alloc-util.h" #include "env-file.h" #include "hostname-util.h" +#include "log.h" #include "os-util.h" #include "string-util.h" #include "strv.h" -char* get_default_hostname(void) { +char* get_default_hostname_raw(void) { int r; + /* Returns the default hostname, and leaves any ??? in place. */ + const char *e = secure_getenv("SYSTEMD_DEFAULT_HOSTNAME"); if (e) { - if (hostname_is_valid(e, 0)) + if (hostname_is_valid(e, VALID_HOSTNAME_QUESTION_MARK)) return strdup(e); + log_debug("Invalid hostname in $SYSTEMD_DEFAULT_HOSTNAME, ignoring: %s", e); } @@ -29,49 +33,15 @@ char* get_default_hostname(void) { if (r < 0) log_debug_errno(r, "Failed to parse os-release, ignoring: %m"); else if (f) { - if (hostname_is_valid(f, 0)) + if (hostname_is_valid(f, VALID_HOSTNAME_QUESTION_MARK)) return TAKE_PTR(f); + log_debug("Invalid hostname in os-release, ignoring: %s", f); } return strdup(FALLBACK_HOSTNAME); } -int gethostname_full(GetHostnameFlags flags, char **ret) { - _cleanup_free_ char *buf = NULL, *fallback = NULL; - struct utsname u; - const char *s; - - assert(ret); - - assert_se(uname(&u) >= 0); - - s = u.nodename; - if (isempty(s) || streq(s, "(none)") || - (!FLAGS_SET(flags, GET_HOSTNAME_ALLOW_LOCALHOST) && is_localhost(s)) || - (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.')) { - if (!FLAGS_SET(flags, GET_HOSTNAME_FALLBACK_DEFAULT)) - return -ENXIO; - - s = fallback = get_default_hostname(); - if (!s) - return -ENOMEM; - - if (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.') - return -ENXIO; - } - - if (FLAGS_SET(flags, GET_HOSTNAME_SHORT)) - buf = strdupcspn(s, "."); - else - buf = strdup(s); - if (!buf) - return -ENOMEM; - - *ret = TAKE_PTR(buf); - return 0; -} - bool valid_ldh_char(char c) { /* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */ @@ -116,7 +86,7 @@ bool hostname_is_valid(const char *s, ValidHostnameFlags flags) { hyphen = true; } else { - if (!valid_ldh_char(*p)) + if (!valid_ldh_char(*p) && (*p != '?' || !FLAGS_SET(flags, VALID_HOSTNAME_QUESTION_MARK))) return false; dot = false; @@ -158,7 +128,7 @@ char* hostname_cleanup(char *s) { dot = false; hyphen = true; - } else if (valid_ldh_char(*p)) { + } else if (valid_ldh_char(*p) || *p == '?') { *(d++) = *p; dot = false; hyphen = false; diff --git a/src/libnm-systemd-shared/src/basic/hostname-util.h b/src/libnm-systemd-shared/src/basic/hostname-util.h index bcac3d9fb0..4c5abe760f 100644 --- a/src/libnm-systemd-shared/src/basic/hostname-util.h +++ b/src/libnm-systemd-shared/src/basic/hostname-util.h @@ -7,42 +7,14 @@ #include "macro.h" #include "strv.h" -typedef enum GetHostnameFlags { - GET_HOSTNAME_ALLOW_LOCALHOST = 1 << 0, /* accepts "localhost" or friends. */ - GET_HOSTNAME_FALLBACK_DEFAULT = 1 << 1, /* use default hostname if no hostname is set. */ - GET_HOSTNAME_SHORT = 1 << 2, /* kills the FQDN part if present. */ -} GetHostnameFlags; - -int gethostname_full(GetHostnameFlags flags, char **ret); -static inline int gethostname_strict(char **ret) { - return gethostname_full(0, ret); -} - -static inline char* gethostname_malloc(void) { - char *s; - - if (gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &s) < 0) - return NULL; - - return s; -} - -static inline char* gethostname_short_malloc(void) { - char *s; - - if (gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT | GET_HOSTNAME_SHORT, &s) < 0) - return NULL; - - return s; -} - -char* get_default_hostname(void); +char* get_default_hostname_raw(void); bool valid_ldh_char(char c) _const_; typedef enum ValidHostnameFlags { - VALID_HOSTNAME_TRAILING_DOT = 1 << 0, /* Accept trailing dot on multi-label names */ - VALID_HOSTNAME_DOT_HOST = 1 << 1, /* Accept ".host" as valid hostname */ + VALID_HOSTNAME_TRAILING_DOT = 1 << 0, /* Accept trailing dot on multi-label names */ + VALID_HOSTNAME_DOT_HOST = 1 << 1, /* Accept ".host" as valid hostname */ + VALID_HOSTNAME_QUESTION_MARK = 1 << 2, /* Accept "?" as place holder for hashed machine ID value */ } ValidHostnameFlags; bool hostname_is_valid(const char *s, ValidHostnameFlags flags) _pure_; 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 fa28d79dcb..4e39985e27 100644 --- a/src/libnm-systemd-shared/src/basic/in-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/in-addr-util.c @@ -13,6 +13,7 @@ #include "in-addr-util.h" #include "logarithm.h" #include "macro.h" +#include "memory-util.h" #include "parse-util.h" #include "random-util.h" #include "stdio-util.h" @@ -28,7 +29,7 @@ bool in4_addr_is_null(const struct in_addr *a) { bool in6_addr_is_null(const struct in6_addr *a) { assert(a); - return IN6_IS_ADDR_UNSPECIFIED(a); + return eqzero(a->in6_u.u6_addr32); } int in_addr_is_null(int family, const union in_addr_union *u) { @@ -66,7 +67,7 @@ bool in4_addr_is_link_local_dynamic(const struct in_addr *a) { bool in6_addr_is_link_local(const struct in6_addr *a) { assert(a); - return IN6_IS_ADDR_LINKLOCAL(a); + return (a->in6_u.u6_addr32[0] & htobe32(0xffc00000)) == htobe32(0xfe800000); } int in_addr_is_link_local(int family, const union in_addr_union *u) { @@ -100,7 +101,7 @@ bool in4_addr_is_multicast(const struct in_addr *a) { bool in6_addr_is_multicast(const struct in6_addr *a) { assert(a); - return IN6_IS_ADDR_MULTICAST(a); + return a->in6_u.u6_addr8[0] == 0xff; } int in_addr_is_multicast(int family, const union in_addr_union *u) { @@ -136,6 +137,10 @@ bool in4_addr_is_non_local(const struct in_addr *a) { !in4_addr_is_localhost(a); } +static bool in6_addr_is_loopback(const struct in6_addr *a) { + return memcmp(a, &(struct in6_addr) IN6ADDR_LOOPBACK_INIT, sizeof(struct in6_addr)) == 0; +} + int in_addr_is_localhost(int family, const union in_addr_union *u) { assert(u); @@ -143,7 +148,7 @@ int in_addr_is_localhost(int family, const union in_addr_union *u) { return in4_addr_is_localhost(&u->in); if (family == AF_INET6) - return IN6_IS_ADDR_LOOPBACK(&u->in6); + return in6_addr_is_loopback(&u->in6); return -EAFNOSUPPORT; } @@ -156,7 +161,7 @@ int in_addr_is_localhost_one(int family, const union in_addr_union *u) { return be32toh(u->in.s_addr) == UINT32_C(0x7F000001); if (family == AF_INET6) - return IN6_IS_ADDR_LOOPBACK(&u->in6); + return in6_addr_is_loopback(&u->in6); return -EAFNOSUPPORT; } @@ -178,7 +183,7 @@ bool in6_addr_equal(const struct in6_addr *a, const struct in6_addr *b) { assert(a); assert(b); - return IN6_ARE_ADDR_EQUAL(a, b); + return memcmp(a, b, sizeof(struct in6_addr)) == 0; } int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) { @@ -942,7 +947,6 @@ int in_addr_prefix_from_string_auto_full( *ret_prefixlen = k; return 0; - } void in_addr_hash_func(const union in_addr_union *u, int family, struct siphash *state) { diff --git a/src/libnm-systemd-shared/src/basic/include/net/if.h b/src/libnm-systemd-shared/src/basic/include/net/if.h new file mode 100644 index 0000000000..7d5b61ba06 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/include/net/if.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#define IF_NAMESIZE 16 + +extern unsigned int if_nametoindex(const char *__ifname) __THROW; +extern char *if_indextoname(unsigned int __ifindex, char __ifname[IF_NAMESIZE]) __THROW; diff --git a/src/libnm-systemd-shared/src/basic/include/netinet/in.h b/src/libnm-systemd-shared/src/basic/include/netinet/in.h new file mode 100644 index 0000000000..97475ac882 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/include/netinet/in.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include +#include +#include +#include +#include + +#define INET_ADDRSTRLEN 16 +#define INET6_ADDRSTRLEN 46 + +extern const struct in6_addr in6addr_any; /* :: */ +extern const struct in6_addr in6addr_loopback; /* ::1 */ +#define IN6ADDR_ANY_INIT { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } } +#define IN6ADDR_LOOPBACK_INIT { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } } + +typedef uint32_t in_addr_t; diff --git a/src/libnm-systemd-shared/src/basic/io-util.c b/src/libnm-systemd-shared/src/basic/io-util.c index 6bcbef3413..7d7f34f203 100644 --- a/src/libnm-systemd-shared/src/basic/io-util.c +++ b/src/libnm-systemd-shared/src/basic/io-util.c @@ -86,6 +86,7 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { return n; assert((size_t) k <= nbytes); + assert(k <= SSIZE_MAX - n); p += k; nbytes -= k; @@ -188,7 +189,7 @@ int pipe_eof(int fd) { return !!(r & POLLHUP); } -int ppoll_usec(struct pollfd *fds, size_t nfds, usec_t timeout) { +int ppoll_usec_full(struct pollfd *fds, size_t nfds, usec_t timeout, const sigset_t *ss) { int r; assert(fds || nfds == 0); @@ -208,10 +209,10 @@ int ppoll_usec(struct pollfd *fds, size_t nfds, usec_t timeout) { * to handle signals, such as signalfd() or signal handlers. ⚠️ ⚠️ ⚠️ */ - if (nfds == 0) + if (nfds == 0 && timeout == 0) return 0; - r = ppoll(fds, nfds, timeout == USEC_INFINITY ? NULL : TIMESPEC_STORE(timeout), NULL); + r = ppoll(fds, nfds, timeout == USEC_INFINITY ? NULL : TIMESPEC_STORE(timeout), ss); if (r < 0) return -errno; if (r == 0) diff --git a/src/libnm-systemd-shared/src/basic/io-util.h b/src/libnm-systemd-shared/src/basic/io-util.h index e027c1a878..208e168317 100644 --- a/src/libnm-systemd-shared/src/basic/io-util.h +++ b/src/libnm-systemd-shared/src/basic/io-util.h @@ -15,14 +15,18 @@ int flush_fd(int fd); ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll); int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll); -int loop_write_full(int fd, const void *buf, size_t nbytes, usec_t timeout); +int loop_write_full(int fd, const void *buf, size_t nbytes, usec_t timeout) _nonnull_if_nonzero_(2, 3); static inline int loop_write(int fd, const void *buf, size_t nbytes) { return loop_write_full(fd, buf, nbytes, 0); } int pipe_eof(int fd); -int ppoll_usec(struct pollfd *fds, size_t nfds, usec_t timeout); +int ppoll_usec_full(struct pollfd *fds, size_t nfds, usec_t timeout, const sigset_t *ss) _nonnull_if_nonzero_(1, 2); +_nonnull_if_nonzero_(1, 2) static inline int ppoll_usec(struct pollfd *fds, size_t nfds, usec_t timeout) { + return ppoll_usec_full(fds, nfds, timeout, NULL); +} + int fd_wait_for_event(int fd, int event, usec_t timeout); ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length); @@ -42,5 +46,4 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) { return true; return FILE_SIZE_VALID(l); - } diff --git a/src/libnm-systemd-shared/src/basic/iovec-util.h b/src/libnm-systemd-shared/src/basic/iovec-util.h index 868454040b..ace20098c8 100644 --- a/src/libnm-systemd-shared/src/basic/iovec-util.h +++ b/src/libnm-systemd-shared/src/basic/iovec-util.h @@ -12,9 +12,9 @@ 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); +size_t iovec_total_size(const struct iovec *iovec, size_t n) _nonnull_if_nonzero_(1, 2); -bool iovec_increment(struct iovec *iovec, size_t n, size_t k); +bool iovec_increment(struct iovec *iovec, size_t n, size_t k) _nonnull_if_nonzero_(1, 2); static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s) { assert(iovec); @@ -42,7 +42,7 @@ static inline void iovec_done_erase(struct iovec *iovec) { 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); -void iovec_array_free(struct iovec *iovec, size_t n_iovec); +void iovec_array_free(struct iovec *iovec, size_t n_iovec) _nonnull_if_nonzero_(1, 2); static inline int iovec_memcmp(const struct iovec *a, const struct iovec *b) { @@ -55,7 +55,7 @@ static inline int iovec_memcmp(const struct iovec *a, const struct iovec *b) { b ? b->iov_len : 0); } -static inline struct iovec *iovec_memdup(const struct iovec *source, struct iovec *ret) { +static inline struct iovec* iovec_memdup(const struct iovec *source, struct iovec *ret) { assert(ret); if (!iovec_is_set(source)) diff --git a/src/libnm-systemd-shared/src/basic/list.h b/src/libnm-systemd-shared/src/basic/list.h index 10e69541d4..090bdc7b60 100644 --- a/src/libnm-systemd-shared/src/basic/list.h +++ b/src/libnm-systemd-shared/src/basic/list.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "macro.h" + /* The head of the linked list. Use this in the structure that shall * contain the head of the linked list */ #define LIST_HEAD(t,name) \ @@ -203,7 +205,3 @@ free_func(elem); \ head; \ }) - -/* Now include "macro.h", because we want our definition of assert() which the macros above use. We include - * it down here instead of up top, since macro.h pulls in log.h which in turn needs our own definitions. */ -#include "macro.h" diff --git a/src/libnm-systemd-shared/src/basic/locale-util.c b/src/libnm-systemd-shared/src/basic/locale-util.c index 09bdb37300..9121ce01f6 100644 --- a/src/libnm-systemd-shared/src/basic/locale-util.c +++ b/src/libnm-systemd-shared/src/basic/locale-util.c @@ -17,15 +17,16 @@ #include "fileio.h" #include "hashmap.h" #include "locale-util.h" -#include "missing_syscall.h" +#include "log.h" #include "path-util.h" +#include "process-util.h" #include "set.h" #include "string-table.h" #include "string-util.h" #include "strv.h" #include "utf8.h" -static char *normalize_locale(const char *name) { +static char* normalize_locale(const char *name) { const char *e; /* Locale names are weird: glibc has some magic rules when looking for the charset name on disk: it @@ -93,18 +94,15 @@ static int add_locales_from_archive(Set *locales) { uint32_t locrec_offset; }; - const struct locarhead *h; - const struct namehashent *e; - const void *p = MAP_FAILED; - _cleanup_close_ int fd = -EBADF; - size_t sz = 0; - struct stat st; int r; - fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC); + assert(locales); + + _cleanup_close_ int fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC); if (fd < 0) return errno == ENOENT ? 0 : -errno; + struct stat st; if (fstat(fd, &st) < 0) return -errno; @@ -117,11 +115,12 @@ static int add_locales_from_archive(Set *locales) { if (file_offset_beyond_memory_size(st.st_size)) return -EFBIG; - p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + void *p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) return -errno; - h = (const struct locarhead *) p; + const struct namehashent *e; + const struct locarhead *h = p; if (h->magic != 0xde020109 || h->namehash_offset + h->namehash_size > st.st_size || h->string_offset + h->string_size > st.st_size || @@ -154,9 +153,9 @@ static int add_locales_from_archive(Set *locales) { r = 0; - finish: +finish: if (p != MAP_FAILED) - munmap((void*) p, sz); + munmap((void*) p, st.st_size); return r; } @@ -165,6 +164,8 @@ static int add_locales_from_libdir(Set *locales) { _cleanup_closedir_ DIR *dir = NULL; int r; + assert(locales); + dir = opendir("/usr/lib/locale"); if (!dir) return errno == ENOENT ? 0 : -errno; @@ -180,7 +181,7 @@ static int add_locales_from_libdir(Set *locales) { return -ENOMEM; r = set_consume(locales, z); - if (r < 0 && r != -EEXIST) + if (r < 0) return r; } @@ -188,11 +189,10 @@ static int add_locales_from_libdir(Set *locales) { } int get_locales(char ***ret) { - _cleanup_set_free_free_ Set *locales = NULL; - _cleanup_strv_free_ char **l = NULL; + _cleanup_set_free_ Set *locales = NULL; int r; - locales = set_new(&string_hash_ops); + locales = set_new(&string_hash_ops_free); if (!locales) return -ENOMEM; @@ -213,31 +213,25 @@ int get_locales(char ***ret) { free(set_remove(locales, locale)); } - l = set_get_strv(locales); + _cleanup_strv_free_ char **l = set_to_strv(&locales); if (!l) return -ENOMEM; - /* Now, all elements are owned by strv 'l'. Hence, do not call set_free_free(). */ - locales = set_free(locales); - r = getenv_bool("SYSTEMD_LIST_NON_UTF8_LOCALES"); - if (IN_SET(r, -ENXIO, 0)) { - char **a, **b; + if (r <= 0) { + if (!IN_SET(r, -ENXIO, 0)) + log_debug_errno(r, "Failed to parse $SYSTEMD_LIST_NON_UTF8_LOCALES as boolean, ignoring: %m"); /* Filter out non-UTF-8 locales, because it's 2019, by default */ - for (a = b = l; *a; a++) { - - if (endswith(*a, "UTF-8") || - strstr(*a, ".UTF-8@")) + char **b = l; + STRV_FOREACH(a, l) + if (endswith(*a, "UTF-8") || strstr(*a, ".UTF-8@")) *(b++) = *a; else free(*a); - } *b = NULL; - - } else if (r < 0) - log_debug_errno(r, "Failed to parse $SYSTEMD_LIST_NON_UTF8_LOCALES as boolean"); + } strv_sort(l); @@ -283,64 +277,48 @@ int locale_is_installed(const char *name) { return true; } -bool is_locale_utf8(void) { - static int cached_answer = -1; +static bool is_locale_utf8_impl(void) { const char *set; int r; - /* Note that we default to 'true' here, since today UTF8 is - * pretty much supported everywhere. */ - - if (cached_answer >= 0) - goto out; + /* Note that we default to 'true' here, since today UTF8 is pretty much supported everywhere. */ r = secure_getenv_bool("SYSTEMD_UTF8"); - if (r >= 0) { - cached_answer = r; - goto out; - } else if (r != -ENXIO) + if (r >= 0) + return r; + if (r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_UTF8, ignoring: %m"); /* This function may be called from libsystemd, and setlocale() is not thread safe. Assuming yes. */ - if (gettid() != raw_getpid()) { - cached_answer = true; - goto out; - } + if (!is_main_thread()) + return true; - if (!setlocale(LC_ALL, "")) { - cached_answer = true; - goto out; - } + if (!setlocale(LC_ALL, "")) + return true; set = nl_langinfo(CODESET); - if (!set) { - cached_answer = true; - goto out; - } + if (!set || streq(set, "UTF-8")) + return true; - if (streq(set, "UTF-8")) { - cached_answer = true; - goto out; - } - - /* For LC_CTYPE=="C" return true, because CTYPE is effectively - * unset and everything can do to UTF-8 nowadays. */ set = setlocale(LC_CTYPE, NULL); - if (!set) { - cached_answer = true; - goto out; - } + if (!set) + return true; - /* Check result, but ignore the result if C was set - * explicitly. */ - cached_answer = - STR_IN_SET(set, "C", "POSIX") && + /* Unless LC_CTYPE is explicitly overridden, return true. Because here CTYPE is effectively unset + * and everything can do to UTF-8 nowadays. */ + return STR_IN_SET(set, "C", "POSIX") && !getenv("LC_ALL") && !getenv("LC_CTYPE") && !getenv("LANG"); +} -out: - return (bool) cached_answer; +bool is_locale_utf8(void) { + static int cached = -1; + + if (cached < 0) + cached = is_locale_utf8_impl(); + + return cached; } void locale_variables_free(char *l[_VARIABLE_LC_MAX]) { diff --git a/src/libnm-systemd-shared/src/basic/lock-util.h b/src/libnm-systemd-shared/src/basic/lock-util.h index a67d8b2c93..b327b8e561 100644 --- a/src/libnm-systemd-shared/src/basic/lock-util.h +++ b/src/libnm-systemd-shared/src/basic/lock-util.h @@ -5,6 +5,8 @@ /* Include here so consumers have LOCK_{EX,SH,NB} available. */ #include +#include "time-util.h" + typedef struct LockFile { int dir_fd; char *path; diff --git a/src/libnm-systemd-shared/src/basic/log.h b/src/libnm-systemd-shared/src/basic/log.h index 02f3f509d1..9759b28dc0 100644 --- a/src/libnm-systemd-shared/src/basic/log.h +++ b/src/libnm-systemd-shared/src/basic/log.h @@ -7,10 +7,7 @@ #include #include -#include "list.h" #include "macro.h" -#include "ratelimit.h" -#include "stdio-util.h" /* Some structures we reference but don't want to pull in headers for */ struct iovec; @@ -82,9 +79,6 @@ int log_show_tid_from_string(const char *e); * environment should not be called from library code — this is always a job * for the application itself. */ -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); @@ -153,7 +147,7 @@ int log_struct_internal( const char *file, int line, const char *func, - const char *format, ...) _printf_(6,0) _sentinel_; + const char *format, ...) _sentinel_; int log_oom_internal( int level, @@ -188,24 +182,6 @@ int log_dump_internal( const char *func, char *buffer); -/* Logging for various assertions */ -_noreturn_ void log_assert_failed( - const char *text, - const char *file, - int line, - const char *func); - -_noreturn_ void log_assert_failed_unreachable( - const char *file, - int line, - const char *func); - -void log_assert_failed_return( - const char *text, - const char *file, - int line, - const char *func); - #define log_dispatch(level, error, buffer) \ log_dispatch_internal(level, error, PROJECT_FILE, __LINE__, __func__, NULL, NULL, NULL, NULL, buffer) @@ -314,11 +290,15 @@ bool log_on_console(void) _pure_; /* Do a fake formatting of the message string to let the scanner verify the arguments against the format * message. The variable will never be set to true, but we don't tell the compiler that :) */ extern bool _log_message_dummy; -# define LOG_MESSAGE(fmt, ...) "MESSAGE=%.0d" fmt, (_log_message_dummy && printf(fmt, ##__VA_ARGS__)), ##__VA_ARGS__ +# define LOG_ITEM(fmt, ...) "%.0d" fmt, (_log_message_dummy && printf(fmt, ##__VA_ARGS__)), ##__VA_ARGS__ +# define LOG_MESSAGE(fmt, ...) LOG_ITEM("MESSAGE=" fmt, ##__VA_ARGS__) #else +# define LOG_ITEM(fmt, ...) fmt, ##__VA_ARGS__ # define LOG_MESSAGE(fmt, ...) "MESSAGE=" fmt, ##__VA_ARGS__ #endif +#define LOG_MESSAGE_ID(id) LOG_ITEM("MESSAGE_ID=" id) + void log_received_signal(int level, const struct signalfd_siginfo *si); /* If turned on, any requests for a log target involving "syslog" will be implicitly upgraded to the equivalent journal target */ @@ -335,9 +315,6 @@ void log_set_open_when_needed(bool b); * stderr, the console or kmsg */ void log_set_prohibit_ipc(bool b); -void log_set_assert_return_is_critical(bool b); -bool log_get_assert_return_is_critical(void) _pure_; - int log_dup_console(void); int log_syntax_internal( @@ -399,58 +376,6 @@ int log_syntax_parse_error_internal( void log_setup(void); -typedef struct LogRateLimit { - int error; - int level; - RateLimit ratelimit; -} LogRateLimit; - -#define log_ratelimit_internal(_level, _error, _ratelimit, _file, _line, _func, _format, ...) \ -({ \ - int _log_ratelimit_error = (_error); \ - int _log_ratelimit_level = (_level); \ - static LogRateLimit _log_ratelimit = { \ - .ratelimit = (_ratelimit), \ - }; \ - unsigned _num_dropped_errors = ratelimit_num_dropped(&_log_ratelimit.ratelimit); \ - if (_log_ratelimit_error != _log_ratelimit.error || _log_ratelimit_level != _log_ratelimit.level) { \ - ratelimit_reset(&_log_ratelimit.ratelimit); \ - _log_ratelimit.error = _log_ratelimit_error; \ - _log_ratelimit.level = _log_ratelimit_level; \ - } \ - if (log_get_max_level() == LOG_DEBUG || ratelimit_below(&_log_ratelimit.ratelimit)) \ - _log_ratelimit_error = _num_dropped_errors > 0 \ - ? log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format " (Dropped %u similar message(s))", ##__VA_ARGS__, _num_dropped_errors) \ - : log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format, ##__VA_ARGS__); \ - _log_ratelimit_error; \ -}) - -#define log_ratelimit_full_errno(level, error, _ratelimit, format, ...) \ - ({ \ - int _level = (level), _e = (error); \ - _e = (log_get_max_level() >= LOG_PRI(_level)) \ - ? log_ratelimit_internal(_level, _e, _ratelimit, PROJECT_FILE, __LINE__, __func__, format, ##__VA_ARGS__) \ - : -ERRNO_VALUE(_e); \ - _e < 0 ? _e : -ESTRPIPE; \ - }) - -#define log_ratelimit_full(level, _ratelimit, format, ...) \ - log_ratelimit_full_errno(level, 0, _ratelimit, format, ##__VA_ARGS__) - -/* Normal logging */ -#define log_ratelimit_info(...) log_ratelimit_full(LOG_INFO, __VA_ARGS__) -#define log_ratelimit_notice(...) log_ratelimit_full(LOG_NOTICE, __VA_ARGS__) -#define log_ratelimit_warning(...) log_ratelimit_full(LOG_WARNING, __VA_ARGS__) -#define log_ratelimit_error(...) log_ratelimit_full(LOG_ERR, __VA_ARGS__) -#define log_ratelimit_emergency(...) log_ratelimit_full(log_emergency_level(), __VA_ARGS__) - -/* Logging triggered by an errno-like error */ -#define log_ratelimit_info_errno(error, ...) log_ratelimit_full_errno(LOG_INFO, error, __VA_ARGS__) -#define log_ratelimit_notice_errno(error, ...) log_ratelimit_full_errno(LOG_NOTICE, error, __VA_ARGS__) -#define log_ratelimit_warning_errno(error, ...) log_ratelimit_full_errno(LOG_WARNING, error, __VA_ARGS__) -#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) { assert(p); @@ -460,112 +385,3 @@ static inline const char* _log_unset_prefixp(const char **p) { #define LOG_SET_PREFIX(prefix) \ _cleanup_(_log_unset_prefixp) _unused_ const char *CONCATENATE(_cleanup_log_unset_prefix_, UNIQ) = _log_set_prefix(prefix, false); - -/* - * The log context allows attaching extra metadata to log messages written to the journal via log.h. We keep - * track of a thread local log context onto which we can push extra metadata fields that should be logged. - * - * LOG_CONTEXT_PUSH() will add the provided field to the log context and will remove it again when the - * current block ends. LOG_CONTEXT_PUSH_STRV() will do the same but for all fields in the given strv. - * LOG_CONTEXT_PUSHF() is like LOG_CONTEXT_PUSH() but takes a format string and arguments. - * - * Using the macros is as simple as putting them anywhere inside a block to add a field to all following log - * messages logged from inside that block. - * - * void myfunction(...) { - * ... - * - * LOG_CONTEXT_PUSHF("MYMETADATA=%s", "abc"); - * - * // Every journal message logged will now have the MYMETADATA=abc - * // field included. - * } - * - * One special case to note is async code, where we use callbacks that are invoked to continue processing - * when some event occurs. For async code, there's usually an associated "userdata" struct containing all the - * information associated with the async operation. In this "userdata" struct, we can store a log context - * allocated with log_context_new() and freed with log_context_free(). We can then add and remove fields to - * the `fields` member of the log context object and all those fields will be logged along with each log - * message. - */ - -typedef struct LogContext LogContext; - -bool log_context_enabled(void); - -LogContext* log_context_new(const char *key, const char *value); -LogContext* log_context_new_strv(char **fields, bool owned); -LogContext* log_context_new_iov(struct iovec *input_iovec, size_t n_input_iovec, bool owned); - -/* Same as log_context_new(), but frees the given fields strv/iovec on failure. */ -LogContext* log_context_new_strv_consume(char **fields); -LogContext* log_context_new_iov_consume(struct iovec *input_iovec, size_t n_input_iovec); - -LogContext *log_context_ref(LogContext *c); -LogContext *log_context_unref(LogContext *c); - -DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_unref); - -/* Returns the number of attached log context objects. */ -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__)) - -#define LOG_CONTEXT_PUSHF(...) \ - LOG_CONTEXT_PUSH(snprintf_ok((char[LINE_MAX]) {}, LINE_MAX, __VA_ARGS__)) - -#define _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, c) \ - _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new(key, value); - -#define LOG_CONTEXT_PUSH_KEY_VALUE(key, value) \ - _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, UNIQ_T(c, UNIQ)) - -#define _LOG_CONTEXT_PUSH_STRV(strv, c) \ - _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv(strv, /*owned=*/ false); - -#define LOG_CONTEXT_PUSH_STRV(strv) \ - _LOG_CONTEXT_PUSH_STRV(strv, UNIQ_T(c, UNIQ)) - -#define _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, c) \ - _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov(input_iovec, n_input_iovec, /*owned=*/ false); - -#define LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec) \ - _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ)) - -/* LOG_CONTEXT_CONSUME_STR()/LOG_CONTEXT_CONSUME_STRV()/LOG_CONTEXT_CONSUME_IOV() are identical to - * LOG_CONTEXT_PUSH_STR()/LOG_CONTEXT_PUSH_STRV()/LOG_CONTEXT_PUSH_IOV() except they take ownership of the - * given str/strv argument. - */ - -#define _LOG_CONTEXT_CONSUME_STR(s, c, strv) \ - _unused_ _cleanup_strv_free_ strv = strv_new(s); \ - if (!strv) \ - free(s); \ - _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(TAKE_PTR(strv)) - -#define LOG_CONTEXT_CONSUME_STR(s) \ - _LOG_CONTEXT_CONSUME_STR(s, UNIQ_T(c, UNIQ), UNIQ_T(sv, UNIQ)) - -#define _LOG_CONTEXT_CONSUME_STRV(strv, c) \ - _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(strv); - -#define LOG_CONTEXT_CONSUME_STRV(strv) \ - _LOG_CONTEXT_CONSUME_STRV(strv, UNIQ_T(c, UNIQ)) - -#define _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, c) \ - _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov_consume(input_iovec, n_input_iovec); - -#define LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec) \ - _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ)) diff --git a/src/libnm-systemd-shared/src/basic/macro.h b/src/libnm-systemd-shared/src/basic/macro.h index 026ec13637..4ee1ad4d57 100644 --- a/src/libnm-systemd-shared/src/basic/macro.h +++ b/src/libnm-systemd-shared/src/basic/macro.h @@ -103,76 +103,6 @@ static inline size_t GREEDY_ALLOC_ROUND_UP(size_t l) { (type*)( (char *)UNIQ_T(A, uniq) - offsetof(type, member) ); \ }) -#ifdef __COVERITY__ - -/* Use special definitions of assertion macros in order to prevent - * false positives of ASSERT_SIDE_EFFECT on Coverity static analyzer - * for uses of assert_se() and assert_return(). - * - * These definitions make expression go through a (trivial) function - * call to ensure they are not discarded. Also use ! or !! to ensure - * the boolean expressions are seen as such. - * - * This technique has been described and recommended in: - * https://community.synopsys.com/s/question/0D534000046Yuzb/suppressing-assertsideeffect-for-functions-that-allow-for-sideeffects - */ - -extern void __coverity_panic__(void); - -static inline void __coverity_check__(int condition) { - if (!condition) - __coverity_panic__(); -} - -static inline int __coverity_check_and_return__(int condition) { - return condition; -} - -#define assert_message_se(expr, message) __coverity_check__(!!(expr)) - -#define assert_log(expr, message) __coverity_check_and_return__(!!(expr)) - -#else /* ! __COVERITY__ */ - -#define assert_message_se(expr, message) \ - do { \ - if (_unlikely_(!(expr))) \ - log_assert_failed(message, PROJECT_FILE, __LINE__, __func__); \ - } while (false) - -#define assert_log(expr, message) ((_likely_(expr)) \ - ? (true) \ - : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __func__), false)) - -#endif /* __COVERITY__ */ - -#define assert_se(expr) assert_message_se(expr, #expr) - -/* We override the glibc assert() here. */ -#undef assert -#ifdef NDEBUG -#define assert(expr) ({ if (!(expr)) __builtin_unreachable(); }) -#else -#define assert(expr) assert_message_se(expr, #expr) -#endif - -#define assert_not_reached() \ - log_assert_failed_unreachable(PROJECT_FILE, __LINE__, __func__) - -#define assert_return(expr, r) \ - do { \ - if (!assert_log(expr, #expr)) \ - return (r); \ - } while (false) - -#define assert_return_errno(expr, r, err) \ - do { \ - if (!assert_log(expr, #expr)) { \ - errno = err; \ - return (r); \ - } \ - } while (false) - #define return_with_errno(r, err) \ do { \ errno = abs(err); \ @@ -251,59 +181,6 @@ static inline int __coverity_check_and_return__(int condition) { /* Pointers range from NULL to POINTER_MAX */ #define POINTER_MAX ((void*) UINTPTR_MAX) -#define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \ - scope type *name##_ref(type *p) { \ - if (!p) \ - return NULL; \ - \ - /* For type check. */ \ - unsigned *q = &p->n_ref; \ - assert(*q > 0); \ - assert_se(*q < UINT_MAX); \ - \ - (*q)++; \ - return p; \ - } - -#define _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func, scope) \ - scope type *name##_unref(type *p) { \ - if (!p) \ - return NULL; \ - \ - assert(p->n_ref > 0); \ - p->n_ref--; \ - if (p->n_ref > 0) \ - return NULL; \ - \ - return free_func(p); \ - } - -#define DEFINE_TRIVIAL_REF_FUNC(type, name) \ - _DEFINE_TRIVIAL_REF_FUNC(type, name,) -#define DEFINE_PRIVATE_TRIVIAL_REF_FUNC(type, name) \ - _DEFINE_TRIVIAL_REF_FUNC(type, name, static) -#define DEFINE_PUBLIC_TRIVIAL_REF_FUNC(type, name) \ - _DEFINE_TRIVIAL_REF_FUNC(type, name, _public_) - -#define DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func) \ - _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func,) -#define DEFINE_PRIVATE_TRIVIAL_UNREF_FUNC(type, name, free_func) \ - _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func, static) -#define DEFINE_PUBLIC_TRIVIAL_UNREF_FUNC(type, name, free_func) \ - _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func, _public_) - -#define DEFINE_TRIVIAL_REF_UNREF_FUNC(type, name, free_func) \ - DEFINE_TRIVIAL_REF_FUNC(type, name); \ - DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func); - -#define DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(type, name, free_func) \ - DEFINE_PRIVATE_TRIVIAL_REF_FUNC(type, name); \ - DEFINE_PRIVATE_TRIVIAL_UNREF_FUNC(type, name, free_func); - -#define DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(type, name, free_func) \ - DEFINE_PUBLIC_TRIVIAL_REF_FUNC(type, name); \ - DEFINE_PUBLIC_TRIVIAL_UNREF_FUNC(type, name, free_func); - /* A macro to force copying of a variable from memory. This is useful whenever we want to read something from * memory and want to make sure the compiler won't optimize away the destination variable for us. It's not * supposed to be a full CPU memory barrier, i.e. CPU is still allowed to reorder the reads, but it is not @@ -328,12 +205,6 @@ static inline size_t size_add(size_t x, size_t y) { return saturate_add(x, y, SIZE_MAX); } -typedef struct { - int _empty[0]; -} dummy_t; - -assert_cc(sizeof(dummy_t) == 0); - /* A little helper for subtracting 1 off a pointer in a safe UB-free way. This is intended to be used for * loops that count down from a high pointer until some base. A naive loop would implement this like this: * @@ -358,5 +229,3 @@ assert_cc(sizeof(dummy_t) == 0); for (typeof(entry) _va_sentinel_[1] = {}, _entries_[] = { __VA_ARGS__ __VA_OPT__(,) _va_sentinel_[0] }, *_current_ = _entries_; \ ((long)(_current_ - _entries_) < (long)(ELEMENTSOF(_entries_) - 1)) && ({ entry = *_current_; true; }); \ _current_++) - -#include "log.h" diff --git a/src/libnm-systemd-shared/src/basic/memory-util.c b/src/libnm-systemd-shared/src/basic/memory-util.c index ed6024fa4a..b39ec725a9 100644 --- a/src/libnm-systemd-shared/src/basic/memory-util.c +++ b/src/libnm-systemd-shared/src/basic/memory-util.c @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include +#include "alloc-util.h" #include "memory-util.h" -#include "missing_threads.h" size_t page_size(void) { static thread_local size_t pgsz = 0; @@ -40,7 +41,7 @@ bool memeqbyte(uint8_t byte, const void *data, size_t length) { return memcmp(data, p + 16, length) == 0; } -void *memdup_reverse(const void *mem, size_t size) { +void* memdup_reverse(const void *mem, size_t size) { assert(mem); assert(size != 0); @@ -55,3 +56,14 @@ void *memdup_reverse(const void *mem, size_t size) { return p; } + +void* erase_and_free(void *p) { + size_t l; + + if (!p) + return NULL; + + l = MALLOC_SIZEOF_SAFE(p); + explicit_bzero_safe(p, l); + return mfree(p); +} diff --git a/src/libnm-systemd-shared/src/basic/memory-util.h b/src/libnm-systemd-shared/src/basic/memory-util.h index 1f604cc452..443fc3a8ab 100644 --- a/src/libnm-systemd-shared/src/basic/memory-util.h +++ b/src/libnm-systemd-shared/src/basic/memory-util.h @@ -7,7 +7,6 @@ #include #include -#include "alloc-util.h" #include "macro.h" #include "memory-util-fundamental.h" @@ -35,13 +34,16 @@ static inline void* mempcpy_safe(void *dst, const void *src, size_t n) { return mempcpy(dst, src, n); } -#define mempcpy_typesafe(dst, src, n) \ +#define _mempcpy_typesafe(dst, src, n, sz) \ ({ \ - size_t _sz_; \ - assert_se(MUL_SAFE(&_sz_, sizeof((dst)[0]), n)); \ - (typeof((dst)[0])*) mempcpy_safe(dst, src, _sz_); \ + size_t sz; \ + assert_se(MUL_SAFE(&sz, sizeof((dst)[0]), n)); \ + (typeof((dst)[0])*) mempcpy_safe(dst, src, sz); \ }) +#define mempcpy_typesafe(dst, src, n) \ + _mempcpy_typesafe(dst, src, n, UNIQ_T(sz, UNIQ)) + /* 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) @@ -59,19 +61,19 @@ static inline int memcmp_nn(const void *s1, size_t n1, const void *s2, size_t n2 #define zero(x) (memzero(&(x), sizeof(x))) -bool memeqbyte(uint8_t byte, const void *data, size_t length); +bool memeqbyte(uint8_t byte, const void *data, size_t length) _nonnull_if_nonzero_(2, 3); #define memeqzero(data, length) memeqbyte(0x00, data, length) #define eqzero(x) memeqzero(x, sizeof(x)) -static inline void *mempset(void *s, int c, size_t n) { +static inline void* mempset(void *s, int c, size_t n) { memset(s, c, n); - return (uint8_t*)s + n; + return (uint8_t*) s + n; } /* Normal memmem() requires haystack to be nonnull, which is annoying for zero-length buffers */ -static inline void *memmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { +static inline void* memmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { if (needlelen <= 0) return (void*) haystack; @@ -85,7 +87,7 @@ static inline void *memmem_safe(const void *haystack, size_t haystacklen, const return memmem(haystack, haystacklen, needle, needlelen); } -static inline void *mempmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { +static inline void* mempmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { const uint8_t *p; p = memmem_safe(haystack, haystacklen, needle, needlelen); @@ -95,16 +97,7 @@ static inline void *mempmem_safe(const void *haystack, size_t haystacklen, const return (uint8_t*) p + needlelen; } -static inline void* erase_and_free(void *p) { - size_t l; - - if (!p) - return NULL; - - l = MALLOC_SIZEOF_SAFE(p); - explicit_bzero_safe(p, l); - return mfree(p); -} +void* erase_and_free(void *p); static inline void erase_and_freep(void *p) { erase_and_free(*(void**) p); @@ -116,4 +109,57 @@ static inline void erase_char(char *p) { } /* Makes a copy of the buffer with reversed order of bytes */ -void *memdup_reverse(const void *mem, size_t size); +void* memdup_reverse(const void *mem, size_t size); + +#define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \ + scope type *name##_ref(type *p) { \ + if (!p) \ + return NULL; \ + \ + /* For type check. */ \ + unsigned *q = &p->n_ref; \ + assert(*q > 0); \ + assert_se(*q < UINT_MAX); \ + \ + (*q)++; \ + return p; \ + } + +#define _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func, scope) \ + scope type *name##_unref(type *p) { \ + if (!p) \ + return NULL; \ + \ + assert(p->n_ref > 0); \ + p->n_ref--; \ + if (p->n_ref > 0) \ + return NULL; \ + \ + return free_func(p); \ + } + +#define DEFINE_TRIVIAL_REF_FUNC(type, name) \ + _DEFINE_TRIVIAL_REF_FUNC(type, name,) +#define DEFINE_PRIVATE_TRIVIAL_REF_FUNC(type, name) \ + _DEFINE_TRIVIAL_REF_FUNC(type, name, static) +#define DEFINE_PUBLIC_TRIVIAL_REF_FUNC(type, name) \ + _DEFINE_TRIVIAL_REF_FUNC(type, name, _public_) + +#define DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func) \ + _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func,) +#define DEFINE_PRIVATE_TRIVIAL_UNREF_FUNC(type, name, free_func) \ + _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func, static) +#define DEFINE_PUBLIC_TRIVIAL_UNREF_FUNC(type, name, free_func) \ + _DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func, _public_) + +#define DEFINE_TRIVIAL_REF_UNREF_FUNC(type, name, free_func) \ + DEFINE_TRIVIAL_REF_FUNC(type, name); \ + DEFINE_TRIVIAL_UNREF_FUNC(type, name, free_func); + +#define DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(type, name, free_func) \ + DEFINE_PRIVATE_TRIVIAL_REF_FUNC(type, name); \ + DEFINE_PRIVATE_TRIVIAL_UNREF_FUNC(type, name, free_func); + +#define DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(type, name, free_func) \ + DEFINE_PUBLIC_TRIVIAL_REF_FUNC(type, name); \ + DEFINE_PUBLIC_TRIVIAL_UNREF_FUNC(type, name, free_func); diff --git a/src/libnm-systemd-shared/src/basic/mempool.c b/src/libnm-systemd-shared/src/basic/mempool.c index 391f29b667..7bf8340315 100644 --- a/src/libnm-systemd-shared/src/basic/mempool.c +++ b/src/libnm-systemd-shared/src/basic/mempool.c @@ -4,6 +4,7 @@ #include #include "format-util.h" +#include "log.h" #include "macro.h" #include "memory-util.h" #include "mempool.h" diff --git a/src/libnm-systemd-shared/src/basic/missing_fcntl.h b/src/libnm-systemd-shared/src/basic/missing_fcntl.h index a6188879c1..b767186a4a 100644 --- a/src/libnm-systemd-shared/src/basic/missing_fcntl.h +++ b/src/libnm-systemd-shared/src/basic/missing_fcntl.h @@ -3,74 +3,16 @@ #include -#ifndef F_LINUX_SPECIFIC_BASE -#define F_LINUX_SPECIFIC_BASE 1024 -#endif - +/* This is defined since glibc-2.41. */ #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 - -#ifndef F_GETPIPE_SZ -#define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8) -#endif - -#ifndef F_ADD_SEALS -#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) -#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) - -#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ -#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ -#define F_SEAL_GROW 0x0004 /* prevent file from growing */ -#define F_SEAL_WRITE 0x0008 /* prevent writes */ -#endif - -#ifndef F_SEAL_FUTURE_WRITE -#define F_SEAL_FUTURE_WRITE 0x0010 /* prevent future writes while mapped */ +#define F_DUPFD_QUERY 1027 #endif +/* This is defined since glibc-2.39. */ #ifndef F_SEAL_EXEC #define F_SEAL_EXEC 0x0020 /* prevent chmod modifying exec bits */ #endif -#ifndef F_OFD_GETLK -#define F_OFD_GETLK 36 -#define F_OFD_SETLK 37 -#define F_OFD_SETLKW 38 -#endif - -#ifndef MAX_HANDLE_SZ -#define MAX_HANDLE_SZ 128 -#endif - -/* The precise definition of __O_TMPFILE is arch specific; use the - * values defined by the kernel (note: some are hexa, some are octal, - * duplicated as-is from the kernel definitions): - * - alpha, parisc, sparc: each has a specific value; - * - others: they use the "generic" value. - */ - -#ifndef __O_TMPFILE -#if defined(__alpha__) -#define __O_TMPFILE 0100000000 -#elif defined(__parisc__) || defined(__hppa__) -#define __O_TMPFILE 0400000000 -#elif defined(__sparc__) || defined(__sparc64__) -#define __O_TMPFILE 0x2000000 -#else -#define __O_TMPFILE 020000000 -#endif -#endif - -/* a horrid kludge trying to make sure that this will fail on old kernels */ -#ifndef O_TMPFILE -#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) -#endif - /* So O_LARGEFILE is generally implied by glibc, and defined to zero hence, because we only build in LFS * mode. However, when invoking fcntl(F_GETFL) the flag is ORed into the result anyway — glibc does not mask * it away. Which sucks. Let's define the actual value here, so that we can mask it ourselves. @@ -97,6 +39,13 @@ #endif #endif +/* This is defined since glibc-2.39. */ #ifndef AT_HANDLE_FID #define AT_HANDLE_FID AT_REMOVEDIR #endif + +/* On musl, O_ACCMODE is defined as (03|O_SEARCH), unlike glibc which defines it as + * (O_RDONLY|O_WRONLY|O_RDWR). Additionally, O_SEARCH is simply defined as O_PATH. This changes the behaviour + * of O_ACCMODE in certain situations, which we don't want. This definition is copied from glibc and works + * around the problems with musl's definition. */ +#define O_ACCMODE_STRICT (O_RDONLY|O_WRONLY|O_RDWR) diff --git a/src/libnm-systemd-shared/src/basic/missing_fs.h b/src/libnm-systemd-shared/src/basic/missing_fs.h new file mode 100644 index 0000000000..1e2f07e55f --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/missing_fs.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +/* Not exposed yet. Defined at fs/ext4/ext4.h */ +#ifndef EXT4_IOC_RESIZE_FS +#define EXT4_IOC_RESIZE_FS _IOW('f', 16, __u64) +#endif + +/* linux/exportfs.h (33c5ac9175195c36a0b7005aaf503a2e81f117a1, 5.5) */ +#ifndef FILEID_KERNFS +#define FILEID_KERNFS 0xfe +#endif diff --git a/src/libnm-systemd-shared/src/basic/missing_random.h b/src/libnm-systemd-shared/src/basic/missing_random.h index 5f40c4e58c..690021969e 100644 --- a/src/libnm-systemd-shared/src/basic/missing_random.h +++ b/src/libnm-systemd-shared/src/basic/missing_random.h @@ -1,26 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + #include "macro.h" -#if HAVE_GETRANDOM -# include -#else -# include -#endif - -#ifndef GRND_NONBLOCK -# define GRND_NONBLOCK 0x0001 -#else -assert_cc(GRND_NONBLOCK == 0x0001); -#endif - -#ifndef GRND_RANDOM -# define GRND_RANDOM 0x0002 -#else -assert_cc(GRND_RANDOM == 0x0002); -#endif - +/* Defined since glibc-2.32. */ #ifndef GRND_INSECURE # define GRND_INSECURE 0x0004 #else diff --git a/src/libnm-systemd-shared/src/basic/missing_socket.h b/src/libnm-systemd-shared/src/basic/missing_socket.h index 8460ce13bf..874e93f37e 100644 --- a/src/libnm-systemd-shared/src/basic/missing_socket.h +++ b/src/libnm-systemd-shared/src/basic/missing_socket.h @@ -3,75 +3,36 @@ #include -#ifndef AF_VSOCK -#define AF_VSOCK 40 -#endif - -#ifndef SO_REUSEPORT -#define SO_REUSEPORT 15 -#endif - -#ifndef SO_PEERGROUPS -#define SO_PEERGROUPS 59 -#endif - +/* Supported since kernel v6.5 (5e2ff6704a275be009be8979af17c52361b79b89) */ #ifndef SO_PASSPIDFD #define SO_PASSPIDFD 76 #endif +/* Supported since kernel v6.5 (7b26952a91cf65ff1cc867a2382a8964d8c0ee7d) */ #ifndef SO_PEERPIDFD #define SO_PEERPIDFD 77 #endif -#ifndef SO_BINDTOIFINDEX -#define SO_BINDTOIFINDEX 62 -#endif - -#ifndef SOL_NETLINK -#define SOL_NETLINK 270 -#endif - -#ifndef SOL_ALG -#define SOL_ALG 279 -#endif - /* Not exposed yet. Defined in include/linux/socket.h. */ #ifndef SOL_SCTP #define SOL_SCTP 132 #endif +/* Supported since kernel v2.6.17 (2c7946a7bf45ae86736ab3b43d0085e43947945c). + * Defined since glibc-2.39 */ #ifndef SCM_SECURITY #define SCM_SECURITY 0x03 #endif +/* Supported since kernel v6.5 (5e2ff6704a275be009be8979af17c52361b79b89). + * Defined since glibc-2.39 */ #ifndef SCM_PIDFD #define SCM_PIDFD 0x04 #endif -/* netinet/in.h */ -#ifndef IP_FREEBIND -#define IP_FREEBIND 15 -#endif - -#ifndef IP_TRANSPARENT -#define IP_TRANSPARENT 19 -#endif - -#ifndef IPV6_FREEBIND -#define IPV6_FREEBIND 78 -#endif - -#ifndef IP_RECVFRAGSIZE -#define IP_RECVFRAGSIZE 25 -#endif - -#ifndef IPV6_RECVFRAGSIZE -#define IPV6_RECVFRAGSIZE 77 -#endif - -/* 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. */ +/* The maximum number of fds that SCM_RIGHTS accepts. This is an internal kernel constant defined in + * include/net/scm.h, 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_stat.h b/src/libnm-systemd-shared/src/basic/missing_stat.h deleted file mode 100644 index eba1a3876f..0000000000 --- a/src/libnm-systemd-shared/src/basic/missing_stat.h +++ /dev/null @@ -1,135 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include -#include - -#if WANT_LINUX_STAT_H -#include -#endif - -/* The newest definition we are aware of (fa2fcf4f1df1559a0a4ee0f46915b496cc2ebf60; 5.8) */ -#define STATX_DEFINITION { \ - __u32 stx_mask; \ - __u32 stx_blksize; \ - __u64 stx_attributes; \ - __u32 stx_nlink; \ - __u32 stx_uid; \ - __u32 stx_gid; \ - __u16 stx_mode; \ - __u16 __spare0[1]; \ - __u64 stx_ino; \ - __u64 stx_size; \ - __u64 stx_blocks; \ - __u64 stx_attributes_mask; \ - struct statx_timestamp stx_atime; \ - struct statx_timestamp stx_btime; \ - struct statx_timestamp stx_ctime; \ - struct statx_timestamp stx_mtime; \ - __u32 stx_rdev_major; \ - __u32 stx_rdev_minor; \ - __u32 stx_dev_major; \ - __u32 stx_dev_minor; \ - __u64 stx_mnt_id; \ - __u64 __spare2; \ - __u64 __spare3[12]; \ -} - -#if !HAVE_STRUCT_STATX -struct statx_timestamp { - __s64 tv_sec; - __u32 tv_nsec; - __s32 __reserved; -}; - -struct statx STATX_DEFINITION; -#endif - -/* Always define the newest version we are aware of as a distinct type, so that we can use it even if glibc - * defines an older definition */ -struct new_statx STATX_DEFINITION; - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef AT_STATX_SYNC_AS_STAT -#define AT_STATX_SYNC_AS_STAT 0x0000 -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef AT_STATX_FORCE_SYNC -#define AT_STATX_FORCE_SYNC 0x2000 -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef AT_STATX_DONT_SYNC -#define AT_STATX_DONT_SYNC 0x4000 -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_TYPE -#define STATX_TYPE 0x00000001U -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_MODE -#define STATX_MODE 0x00000002U -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_NLINK -#define STATX_NLINK 0x00000004U -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_UID -#define STATX_UID 0x00000008U -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_GID -#define STATX_GID 0x00000010U -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_ATIME -#define STATX_ATIME 0x00000020U -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_MTIME -#define STATX_MTIME 0x00000040U -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_CTIME -#define STATX_CTIME 0x00000080U -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_INO -#define STATX_INO 0x00000100U -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_SIZE -#define STATX_SIZE 0x00000200U -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_BLOCKS -#define STATX_BLOCKS 0x00000400U -#endif - -/* a528d35e8bfcc521d7cb70aaf03e1bd296c8493f (4.11) */ -#ifndef STATX_BTIME -#define STATX_BTIME 0x00000800U -#endif - -/* fa2fcf4f1df1559a0a4ee0f46915b496cc2ebf60 (5.8) */ -#ifndef STATX_MNT_ID -#define STATX_MNT_ID 0x00001000U -#endif - -/* 80340fe3605c0e78cfe496c3b3878be828cfdbfe (5.8) */ -#ifndef STATX_ATTR_MOUNT_ROOT -#define STATX_ATTR_MOUNT_ROOT 0x00002000 /* Root of a mount */ -#endif diff --git a/src/libnm-systemd-shared/src/basic/missing_syscall.h b/src/libnm-systemd-shared/src/basic/missing_syscall.h index 37048e1bc0..296e39b919 100644 --- a/src/libnm-systemd-shared/src/basic/missing_syscall.h +++ b/src/libnm-systemd-shared/src/basic/missing_syscall.h @@ -4,12 +4,7 @@ /* Missing glibc definitions to access certain kernel APIs */ #include -#include -#if HAVE_LINUX_TIME_TYPES_H -/* This header defines __kernel_timespec for us, but is only available since Linux 5.1, hence conditionally - * include this. */ #include -#endif #include #include #include @@ -23,24 +18,14 @@ #include "macro.h" #include "missing_keyctl.h" #include "missing_sched.h" -#include "missing_stat.h" #include "missing_syscall_def.h" -/* linux/kcmp.h */ -#ifndef KCMP_FILE /* 3f4994cfc15f38a3159c6e3a4b3ab2e1481a6b02 (3.19) */ -#define KCMP_FILE 0 -#endif - /* ======================================================================= */ #if !HAVE_FCHMODAT2 +/* since kernel v6.6 (78252deb023cf0879256fcfbafe37022c390762b) */ static inline int missing_fchmodat2(int dirfd, const char *path, mode_t mode, int flags) { -# ifdef __NR_fchmodat2 return syscall(__NR_fchmodat2, dirfd, path, mode, flags); -# else - errno = ENOSYS; - return -1; -# endif } # define fchmodat2 missing_fchmodat2 @@ -78,93 +63,6 @@ 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) { - return syscall(__NR_memfd_create, name, flags); -} - -# define memfd_create missing_memfd_create -#endif - -/* ======================================================================= */ - -#if !HAVE_GETRANDOM -/* glibc says getrandom() returns ssize_t */ -static inline ssize_t missing_getrandom(void *buffer, size_t count, unsigned flags) { - return syscall(__NR_getrandom, buffer, count, flags); -} - -# define getrandom missing_getrandom -#endif - -/* ======================================================================= */ - -/* The syscall has been defined since forever, but the glibc wrapper was missing. */ -#if !HAVE_GETTID -static inline pid_t missing_gettid(void) { -# if defined __NR_gettid && __NR_gettid >= 0 - return (pid_t) syscall(__NR_gettid); -# else -# error "__NR_gettid not defined" -# endif -} - -# define gettid missing_gettid -#endif - -/* ======================================================================= */ - -#if !HAVE_NAME_TO_HANDLE_AT -struct file_handle { - unsigned int handle_bytes; - int handle_type; - unsigned char f_handle[0]; -}; - -static inline int missing_name_to_handle_at(int fd, const char *name, struct file_handle *handle, int *mnt_id, int flags) { -# ifdef __NR_name_to_handle_at - return syscall(__NR_name_to_handle_at, fd, name, handle, mnt_id, flags); -# else - errno = ENOSYS; - return -1; -# endif -} - -# define name_to_handle_at missing_name_to_handle_at -#endif - -/* ======================================================================= */ - -#if !HAVE_SETNS -static inline int missing_setns(int fd, int nstype) { - return syscall(__NR_setns, fd, nstype); -} - -# define setns missing_setns -#endif - -/* ======================================================================= */ - -static inline pid_t raw_getpid(void) { -#if defined(__alpha__) - return (pid_t) syscall(__NR_getxpid); -#else - return (pid_t) syscall(__NR_getpid); -#endif -} - -/* ======================================================================= */ - -#if !HAVE_RENAMEAT2 -static inline int missing_renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) { - return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags); -} - -# define renameat2 missing_renameat2 -#endif - -/* ======================================================================= */ - #if !HAVE_KCMP static inline int missing_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) { return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2); @@ -182,12 +80,16 @@ static inline long missing_keyctl(int cmd, unsigned long arg2, unsigned long arg # 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) { return syscall(__NR_add_key, type, description, payload, plen, ringid); # 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) { return syscall(__NR_request_key, type, description, callout_info, destringid); @@ -197,34 +99,11 @@ static inline key_serial_t missing_request_key(const char *type, const char *des /* ======================================================================= */ -#if !HAVE_COPY_FILE_RANGE -static inline ssize_t missing_copy_file_range(int fd_in, loff_t *off_in, - int fd_out, loff_t *off_out, - size_t len, - unsigned int flags) { -# ifdef __NR_copy_file_range - return syscall(__NR_copy_file_range, fd_in, off_in, fd_out, off_out, len, flags); -# else - errno = ENOSYS; - return -1; -# endif -} - -# define copy_file_range missing_copy_file_range -#endif - -/* ======================================================================= */ - #if !HAVE_BPF union bpf_attr; static inline int missing_bpf(int cmd, union bpf_attr *attr, size_t size) { -#ifdef __NR_bpf return (int) syscall(__NR_bpf, cmd, attr, size); -#else - errno = ENOSYS; - return -1; -#endif } # define bpf missing_bpf @@ -232,28 +111,6 @@ static inline int missing_bpf(int cmd, union bpf_attr *attr, size_t size) { /* ======================================================================= */ -#if !HAVE_STATX -struct statx; - -static inline ssize_t missing_statx(int dfd, const char *filename, unsigned flags, unsigned int mask, struct statx *buffer) { -# ifdef __NR_statx - return syscall(__NR_statx, dfd, filename, flags, mask, buffer); -# else - errno = ENOSYS; - return -1; -# endif -} -#endif - -/* This typedef is supposed to be always defined. */ -typedef struct statx struct_statx; - -#if !HAVE_STATX -# define statx(dfd, filename, flags, mask, buffer) missing_statx(dfd, filename, flags, mask, buffer) -#endif - -/* ======================================================================= */ - #if !HAVE_SET_MEMPOLICY enum { MPOL_DEFAULT, @@ -265,14 +122,7 @@ enum { static inline long missing_set_mempolicy(int mode, const unsigned long *nodemask, unsigned long maxnode) { - long i; -# if defined __NR_set_mempolicy && __NR_set_mempolicy >= 0 - i = syscall(__NR_set_mempolicy, mode, nodemask, maxnode); -# else - errno = ENOSYS; - i = -1; -# endif - return i; + return syscall(__NR_set_mempolicy, mode, nodemask, maxnode); } # define set_mempolicy missing_set_mempolicy @@ -282,14 +132,7 @@ static inline long missing_set_mempolicy(int mode, const unsigned long *nodemask static inline long missing_get_mempolicy(int *mode, unsigned long *nodemask, unsigned long maxnode, void *addr, unsigned long flags) { - long i; -# if defined __NR_get_mempolicy && __NR_get_mempolicy >= 0 - i = syscall(__NR_get_mempolicy, mode, nodemask, maxnode, addr, flags); -# else - errno = ENOSYS; - i = -1; -# endif - return i; + return syscall(__NR_get_mempolicy, mode, nodemask, maxnode, addr, flags); } # define get_mempolicy missing_get_mempolicy @@ -298,6 +141,7 @@ static inline long missing_get_mempolicy(int *mode, unsigned long *nodemask, /* ======================================================================= */ #if !HAVE_PIDFD_SEND_SIGNAL +/* since kernel v5.1 (3eb39f47934f9d5a3027fe00d906a45fe3a15fad) */ static inline int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags) { return syscall(__NR_pidfd_send_signal, fd, sig, info, flags); } @@ -305,7 +149,10 @@ static inline int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, un # define pidfd_send_signal missing_pidfd_send_signal #endif +/* ======================================================================= */ + #if !HAVE_PIDFD_OPEN +/* since kernel v5.3 (7615d9e1780e26e0178c93c55b73309a5dc093d7) */ static inline int missing_pidfd_open(pid_t pid, unsigned flags) { return syscall(__NR_pidfd_open, pid, flags); } @@ -315,27 +162,9 @@ static inline int missing_pidfd_open(pid_t pid, unsigned flags) { /* ======================================================================= */ -#if !HAVE_RT_SIGQUEUEINFO -static inline int missing_rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t *info) { -# if defined __NR_rt_sigqueueinfo && __NR_rt_sigqueueinfo >= 0 - return syscall(__NR_rt_sigqueueinfo, tgid, sig, info); -# else -# error "__NR_rt_sigqueueinfo not defined" -# endif -} - -# define rt_sigqueueinfo missing_rt_sigqueueinfo -#endif - -/* ======================================================================= */ - #if !HAVE_RT_TGSIGQUEUEINFO static inline int missing_rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info) { -# if defined __NR_rt_tgsigqueueinfo && __NR_rt_tgsigqueueinfo >= 0 return syscall(__NR_rt_tgsigqueueinfo, tgid, tid, sig, info); -# else -# error "__NR_rt_tgsigqueueinfo not defined" -# endif } # define rt_tgsigqueueinfo missing_rt_tgsigqueueinfo @@ -344,27 +173,21 @@ static inline int missing_rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, sigi /* ======================================================================= */ #if !HAVE_EXECVEAT +/* since kernel v3.19 (51f39a1f0cea1cacf8c787f652f26dfee9611874) */ static inline int missing_execveat(int dirfd, const char *pathname, char *const argv[], char *const envp[], int flags) { -# if defined __NR_execveat && __NR_execveat >= 0 return syscall(__NR_execveat, dirfd, pathname, argv, envp, flags); -# else - errno = ENOSYS; - return -1; -# endif } -# undef AT_EMPTY_PATH -# define AT_EMPTY_PATH 0x1000 # define execveat missing_execveat #endif /* ======================================================================= */ #if !HAVE_CLOSE_RANGE +/* since kernel v5.9 (9b4feb630e8e9801603f3cab3a36369e3c1cf88d) */ 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. 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. */ @@ -372,10 +195,6 @@ static inline int missing_close_range(unsigned first_fd, unsigned end_fd, unsign first_fd, end_fd, flags); -# else - errno = ENOSYS; - return -1; -# endif } # define close_range missing_close_range @@ -383,243 +202,8 @@ static inline int missing_close_range(unsigned first_fd, unsigned end_fd, unsign /* ======================================================================= */ -#if !HAVE_MOUNT_SETATTR - -#if !HAVE_STRUCT_MOUNT_ATTR -struct mount_attr { - uint64_t attr_set; - uint64_t attr_clr; - uint64_t propagation; - uint64_t userns_fd; -}; -#else -struct mount_attr; -#endif - -#ifndef MOUNT_ATTR_RDONLY -#define MOUNT_ATTR_RDONLY 0x00000001 /* Mount read-only */ -#endif - -#ifndef MOUNT_ATTR_NOSUID -#define MOUNT_ATTR_NOSUID 0x00000002 /* Ignore suid and sgid bits */ -#endif - -#ifndef MOUNT_ATTR_NODEV -#define MOUNT_ATTR_NODEV 0x00000004 /* Disallow access to device special files */ -#endif - -#ifndef MOUNT_ATTR_NOEXEC -#define MOUNT_ATTR_NOEXEC 0x00000008 /* Disallow program execution */ -#endif - -#ifndef MOUNT_ATTR__ATIME -#define MOUNT_ATTR__ATIME 0x00000070 /* Setting on how atime should be updated */ -#endif - -#ifndef MOUNT_ATTR_RELATIME -#define MOUNT_ATTR_RELATIME 0x00000000 /* - Update atime relative to mtime/ctime. */ -#endif - -#ifndef MOUNT_ATTR_NOATIME -#define MOUNT_ATTR_NOATIME 0x00000010 /* - Do not update access times. */ -#endif - -#ifndef MOUNT_ATTR_STRICTATIME -#define MOUNT_ATTR_STRICTATIME 0x00000020 /* - Always perform atime updates */ -#endif - -#ifndef MOUNT_ATTR_NODIRATIME -#define MOUNT_ATTR_NODIRATIME 0x00000080 /* Do not update directory access times */ -#endif - -#ifndef MOUNT_ATTR_IDMAP -#define MOUNT_ATTR_IDMAP 0x00100000 /* Idmap mount to @userns_fd in struct mount_attr. */ -#endif - -#ifndef MOUNT_ATTR_NOSYMFOLLOW -#define MOUNT_ATTR_NOSYMFOLLOW 0x00200000 /* Do not follow symlinks */ -#endif - -#ifndef MOUNT_ATTR_SIZE_VER0 -#define MOUNT_ATTR_SIZE_VER0 32 /* sizeof first published struct */ -#endif - -#ifndef AT_RECURSIVE -#define AT_RECURSIVE 0x8000 -#endif - -static inline int missing_mount_setattr( - int dfd, - const char *path, - unsigned flags, - struct mount_attr *attr, - size_t size) { - -# if defined __NR_mount_setattr && __NR_mount_setattr >= 0 - return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); -# else - errno = ENOSYS; - return -1; -# endif -} - -# define mount_setattr missing_mount_setattr -#endif - -/* ======================================================================= */ - -#if !HAVE_OPEN_TREE - -#ifndef OPEN_TREE_CLONE -#define OPEN_TREE_CLONE 1 -#endif - -#ifndef OPEN_TREE_CLOEXEC -#define OPEN_TREE_CLOEXEC O_CLOEXEC -#endif - -static inline int missing_open_tree( - int dfd, - const char *filename, - unsigned flags) { - -# if defined __NR_open_tree && __NR_open_tree >= 0 - return syscall(__NR_open_tree, dfd, filename, flags); -# else - errno = ENOSYS; - return -1; -# endif -} - -# define open_tree missing_open_tree -#endif - -/* ======================================================================= */ - -#ifndef MOVE_MOUNT_BENEATH -#define MOVE_MOUNT_BENEATH 0x00000200 -#endif - -#if !HAVE_MOVE_MOUNT - -#ifndef MOVE_MOUNT_F_EMPTY_PATH -#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */ -#endif - -#ifndef MOVE_MOUNT_T_EMPTY_PATH -#define MOVE_MOUNT_T_EMPTY_PATH 0x00000040 /* Empty to path permitted */ -#endif - -static inline int missing_move_mount( - int from_dfd, - const char *from_pathname, - int to_dfd, - const char *to_pathname, - unsigned flags) { - -# if defined __NR_move_mount && __NR_move_mount >= 0 - return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags); -# else - errno = ENOSYS; - return -1; -# endif -} - -# define move_mount missing_move_mount -#endif - -/* ======================================================================= */ - -#if !HAVE_FSOPEN - -#ifndef FSOPEN_CLOEXEC -#define FSOPEN_CLOEXEC 0x00000001 -#endif - -static inline int missing_fsopen(const char *fsname, unsigned flags) { -# if defined __NR_fsopen && __NR_fsopen >= 0 - return syscall(__NR_fsopen, fsname, flags); -# else - errno = ENOSYS; - return -1; -# endif -} - -# define fsopen missing_fsopen -#endif - -/* ======================================================================= */ - -#if !HAVE_FSCONFIG - -#ifndef FSCONFIG_SET_FLAG -#define FSCONFIG_SET_FLAG 0 /* Set parameter, supplying no value */ -#endif - -#ifndef FSCONFIG_SET_STRING -#define FSCONFIG_SET_STRING 1 /* Set parameter, supplying a string value */ -#endif - -#ifndef FSCONFIG_SET_FD -#define FSCONFIG_SET_FD 5 /* Set parameter, supplying an object by fd */ -#endif - -#ifndef FSCONFIG_CMD_CREATE -#define FSCONFIG_CMD_CREATE 6 /* Invoke superblock creation */ -#endif - -static inline int missing_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux) { -# if defined __NR_fsconfig && __NR_fsconfig >= 0 - return syscall(__NR_fsconfig, fd, cmd, key, value, aux); -# else - errno = ENOSYS; - return -1; -# endif -} - -# define fsconfig missing_fsconfig -#endif - -/* ======================================================================= */ - -#if !HAVE_FSMOUNT - -#ifndef FSMOUNT_CLOEXEC -#define FSMOUNT_CLOEXEC 0x00000001 -#endif - -static inline int missing_fsmount(int fd, unsigned flags, unsigned ms_flags) { -# if defined __NR_fsmount && __NR_fsmount >= 0 - return syscall(__NR_fsmount, fd, flags, ms_flags); -# else - errno = ENOSYS; - return -1; -# endif -} - -# define fsmount missing_fsmount -#endif - -/* ======================================================================= */ - -#if !HAVE_GETDENTS64 - -static inline ssize_t missing_getdents64(int fd, void *buffer, size_t length) { -# if defined __NR_getdents64 && __NR_getdents64 >= 0 - return syscall(__NR_getdents64, fd, buffer, length); -# else - errno = ENOSYS; - return -1; -# endif -} - -# define getdents64 missing_getdents64 -#endif - -/* ======================================================================= */ - #if !HAVE_SCHED_SETATTR - +/* since kernel 3.14 (e6cfc0295c7d51b008999a8b13a44fb43f8685ea) */ 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); } @@ -644,15 +228,38 @@ int __clone2(int (*fn)(void *), void *stack_base, size_t stack_size, int flags, /* ======================================================================= */ #if !HAVE_QUOTACTL_FD - +/* since kernel v5.14 (64c2c2c62f92339b176ea24403d8db16db36f9e6) */ 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 + +/* ======================================================================= */ + +#if !HAVE_SETXATTRAT +/* since kernel v6.13 (6140be90ec70c39fa844741ca3cc807dd0866394) */ +struct xattr_args { + _align_(8) uint64_t value; + uint32_t size; + uint32_t flags; +}; + +static inline int missing_setxattrat(int fd, const char *path, int at_flags, const char *name, const struct xattr_args *args, size_t size) { + return syscall(__NR_setxattrat, fd, path, at_flags, name, args, size); +} + +# define setxattrat missing_setxattrat +#endif + +/* ======================================================================= */ + +#if !HAVE_REMOVEXATTRAT +/* since kernel v6.13 (6140be90ec70c39fa844741ca3cc807dd0866394) */ +static inline int missing_removexattrat(int fd, const char *path, int at_flags, const char *name) { + return syscall(__NR_removexattrat, fd, path, at_flags, name); +} + +# define removexattrat missing_removexattrat +#endif diff --git a/src/libnm-systemd-shared/src/basic/missing_threads.h b/src/libnm-systemd-shared/src/basic/missing_threads.h deleted file mode 100644 index c7da1dbd5e..0000000000 --- a/src/libnm-systemd-shared/src/basic/missing_threads.h +++ /dev/null @@ -1,13 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* If threads.h doesn't exist, then define our own thread_local to match C11's thread_local. */ -#if HAVE_THREADS_H -# include -#elif !(defined(thread_local)) -# ifndef __STDC_NO_THREADS__ -# define thread_local _Thread_local -# else -# define thread_local __thread -# endif -#endif diff --git a/src/libnm-systemd-shared/src/basic/missing_type.h b/src/libnm-systemd-shared/src/basic/missing_type.h deleted file mode 100644 index 1d17705c35..0000000000 --- a/src/libnm-systemd-shared/src/basic/missing_type.h +++ /dev/null @@ -1,12 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include - -#if !HAVE_CHAR32_T -# define char32_t uint32_t -#endif - -#if !HAVE_CHAR16_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 index 3965b5bdbf..05648779e3 100644 --- a/src/libnm-systemd-shared/src/basic/missing_wait.h +++ b/src/libnm-systemd-shared/src/basic/missing_wait.h @@ -5,6 +5,7 @@ #include "macro.h" +/* since glibc-2.36 */ #ifndef P_PIDFD # define P_PIDFD 3 #else diff --git a/src/libnm-systemd-shared/src/basic/mountpoint-util.c b/src/libnm-systemd-shared/src/basic/mountpoint-util.c new file mode 100644 index 0000000000..23653160a0 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/mountpoint-util.c @@ -0,0 +1,819 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "alloc-util.h" +#include "chase.h" +#include "fd-util.h" +#include "fileio.h" +#include "filesystems.h" +#include "fs-util.h" +#include "log.h" +#include "missing_fcntl.h" +#include "missing_fs.h" +#include "missing_syscall.h" +#include "mkdir.h" +#include "mountpoint-util.h" +#include "nulstr-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "user-util.h" + +/* This is the original MAX_HANDLE_SZ definition from the kernel, when the API was introduced. We use that in place of + * any more currently defined value to future-proof things: if the size is increased in the API headers, and our code + * is recompiled then it would cease working on old kernels, as those refuse any sizes larger than this value with + * EINVAL right-away. Hence, let's disconnect ourselves from any such API changes, and stick to the original definition + * from when it was introduced. We use it as a start value only anyway (see below), and hence should be able to deal + * with large file handles anyway. */ +#define ORIGINAL_MAX_HANDLE_SZ 128 + +bool is_name_to_handle_at_fatal_error(int err) { + /* name_to_handle_at() can return "acceptable" errors that are due to the context. For example + * the file system does not support name_to_handle_at() (EOPNOTSUPP), or the syscall was blocked + * (EACCES/EPERM; maybe through seccomp, because we are running inside of a container), or + * the mount point is not triggered yet (EOVERFLOW, think autofs+nfs4), or some general name_to_handle_at() + * flakiness (EINVAL). However other errors are not supposed to happen and therefore are considered + * fatal ones. */ + + assert(err < 0); + + if (ERRNO_IS_NEG_NOT_SUPPORTED(err)) + return false; + if (ERRNO_IS_NEG_PRIVILEGE(err)) + return false; + + return !IN_SET(err, -EOVERFLOW, -EINVAL); +} + +int name_to_handle_at_loop( + int fd, + const char *path, + struct file_handle **ret_handle, + int *ret_mnt_id, + int flags) { + + size_t n = ORIGINAL_MAX_HANDLE_SZ; + + assert(fd >= 0 || fd == AT_FDCWD); + assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH|AT_HANDLE_FID)) == 0); + + /* We need to invoke name_to_handle_at() in a loop, given that it might return EOVERFLOW when the specified + * buffer is too small. Note that in contrast to what the docs might suggest, MAX_HANDLE_SZ is only good as a + * start value, it is not an upper bound on the buffer size required. + * + * This improves on raw name_to_handle_at() also in one other regard: ret_handle and ret_mnt_id can be passed + * as NULL if there's no interest in either. */ + + for (;;) { + _cleanup_free_ struct file_handle *h = NULL; + int mnt_id = -1; + + h = malloc0(offsetof(struct file_handle, f_handle) + n); + if (!h) + return -ENOMEM; + + h->handle_bytes = n; + + if (name_to_handle_at(fd, strempty(path), h, &mnt_id, flags) >= 0) { + + if (ret_handle) + *ret_handle = TAKE_PTR(h); + + if (ret_mnt_id) + *ret_mnt_id = mnt_id; + + return 0; + } + if (errno != EOVERFLOW) + return -errno; + + if (!ret_handle && ret_mnt_id && mnt_id >= 0) { + + /* As it appears, name_to_handle_at() fills in mnt_id even when it returns EOVERFLOW when the + * buffer is too small, but that's undocumented. Hence, let's make use of this if it appears to + * be filled in, and the caller was interested in only the mount ID an nothing else. */ + + *ret_mnt_id = mnt_id; + return 0; + } + + /* If name_to_handle_at() didn't increase the byte size, then this EOVERFLOW is caused by + * something else (apparently EOVERFLOW is returned for untriggered nfs4 autofs mounts + * sometimes), not by the too small buffer. In that case propagate EOVERFLOW */ + if (h->handle_bytes <= n) + return -EOVERFLOW; + + /* The buffer was too small. Size the new buffer by what name_to_handle_at() returned. */ + n = h->handle_bytes; + + /* paranoia: check for overflow (note that .handle_bytes is unsigned only) */ + if (n > UINT_MAX - offsetof(struct file_handle, f_handle)) + return -EOVERFLOW; + } +} + +int name_to_handle_at_try_fid( + int fd, + const char *path, + struct file_handle **ret_handle, + int *ret_mnt_id, + int flags) { + + int r; + + assert(fd >= 0 || fd == AT_FDCWD); + + /* First issues name_to_handle_at() with AT_HANDLE_FID. If this fails and this is not a fatal error + * we'll try without the flag, in order to support older kernels that didn't have AT_HANDLE_FID + * (i.e. older than Linux 6.5). */ + + r = name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, flags | AT_HANDLE_FID); + if (r >= 0 || is_name_to_handle_at_fatal_error(r)) + return r; + + return name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, flags & ~AT_HANDLE_FID); +} + +static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *ret_mnt_id) { + char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; + _cleanup_close_ int subfd = -EBADF; + int r; + + assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); + assert(ret_mnt_id); + + if ((flags & AT_EMPTY_PATH) && isempty(filename)) + xsprintf(path, "/proc/self/fdinfo/%i", fd); + else { + subfd = openat(fd, filename, O_CLOEXEC|O_PATH|(flags & AT_SYMLINK_FOLLOW ? 0 : O_NOFOLLOW)); + if (subfd < 0) + return -errno; + + xsprintf(path, "/proc/self/fdinfo/%i", subfd); + } + + _cleanup_free_ char *p = NULL; + r = get_proc_field(path, "mnt_id", &p); + if (r == -ENOENT) + return -EBADF; + if (r < 0) + return r; + + return safe_atoi(p, ret_mnt_id); +} + +static bool filename_possibly_with_slash_suffix(const char *s) { + const char *slash, *copied; + + /* Checks whether the specified string is either file name, or a filename with a suffix of + * slashes. But nothing else. + * + * this is OK: foo, bar, foo/, bar/, foo//, bar/// + * this is not OK: "", "/", "/foo", "foo/bar", ".", ".." … */ + + slash = strchr(s, '/'); + if (!slash) + return filename_is_valid(s); + + if (slash - s > PATH_MAX) /* We want to allocate on the stack below, hence do a size check first */ + return false; + + if (slash[strspn(slash, "/")] != 0) /* Check that the suffix consist only of one or more slashes */ + return false; + + copied = strndupa_safe(s, slash - s); + return filename_is_valid(copied); +} + +bool file_handle_equal(const struct file_handle *a, const struct file_handle *b) { + if (a == b) + return true; + if (!a != !b) + return false; + if (a->handle_type != b->handle_type) + return false; + + return memcmp_nn(a->f_handle, a->handle_bytes, b->f_handle, b->handle_bytes) == 0; +} + +int is_mount_point_at(int fd, const char *filename, int flags) { + bool fd_is_self; + int r; + + assert(fd >= 0 || fd == AT_FDCWD); + assert((flags & ~AT_SYMLINK_FOLLOW) == 0); + + if (isempty(filename)) { + if (fd == AT_FDCWD) + filename = "."; + else { + /* If the file name is empty we'll see if the specified 'fd' is a mount point. + * That's only supported by statx(), or if the inode specified via 'fd' refers to a + * directory. Otherwise, we'll have to fail (ENOTDIR), because we have no kernel API + * to query the information we need. */ + flags |= AT_EMPTY_PATH; + filename = ""; + } + + fd_is_self = true; + } else if (STR_IN_SET(filename, ".", "./")) + fd_is_self = true; + else { + /* Insist that the specified filename is actually a filename, and not a path, i.e. some inode + * further up or down the tree then immediately below the specified directory fd. */ + if (!filename_possibly_with_slash_suffix(filename)) + return -EINVAL; + + fd_is_self = false; + } + + /* First we will try statx()' STATX_ATTR_MOUNT_ROOT attribute, which is our ideal API, available + * since kernel 5.8. + * + * If that fails, our second try is the name_to_handle_at() syscall, which tells us the mount id and + * an opaque file "handle". It is not supported everywhere though (kernel compile-time option, not + * all file systems are hooked up). If it works the mount id is usually good enough to tell us + * whether something is a mount point. + * + * If that didn't work we will try to read the mount id from /proc/self/fdinfo/. This is almost + * as good as name_to_handle_at(), however, does not return the opaque file handle. The opaque file + * handle is pretty useful to detect the root directory, which we should always consider a mount + * point. Hence we use this only as fallback. + * + * Note that traditionally the check is done via fstat()-based st_dev comparisons. However, various + * file systems don't guarantee same st_dev across single fs anymore, e.g. unionfs exposes file systems + * with a variety of st_dev reported. Also, btrfs subvolumes have different st_dev, even though + * they aren't real mounts of their own. */ + + struct statx sx = {}; /* explicitly initialize the struct to make msan silent. */ + if (statx(fd, filename, + at_flags_normalize_nofollow(flags) | + AT_NO_AUTOMOUNT | /* don't trigger automounts – mounts are a local concept, hence no need to trigger automounts to determine STATX_ATTR_MOUNT_ROOT */ + AT_STATX_DONT_SYNC, /* don't go to the network for this – for similar reasons */ + STATX_TYPE, + &sx) < 0) + return -errno; + + if (FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) /* yay! */ + return FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); + + _cleanup_free_ struct file_handle *h = NULL, *h_parent = NULL; + int mount_id = -1, mount_id_parent = -1; + bool nosupp = false; + + r = name_to_handle_at_try_fid(fd, filename, &h, &mount_id, flags); + if (r < 0) { + if (is_name_to_handle_at_fatal_error(r)) + return r; + if (!ERRNO_IS_NOT_SUPPORTED(r)) + goto fallback_fdinfo; + + /* This file system does not support name_to_handle_at(), hence let's see if the upper fs + * supports it (in which case it is a mount point), otherwise fall back to the fdinfo logic. */ + nosupp = true; + } + + if (fd_is_self) + r = name_to_handle_at_try_fid(fd, "..", &h_parent, &mount_id_parent, 0); /* can't work for non-directories 😢 */ + else + r = name_to_handle_at_try_fid(fd, "", &h_parent, &mount_id_parent, AT_EMPTY_PATH); + if (r < 0) { + if (is_name_to_handle_at_fatal_error(r)) + return r; + if (!ERRNO_IS_NOT_SUPPORTED(r)) + goto fallback_fdinfo; + if (nosupp) + /* Both the parent and the directory can't do name_to_handle_at() */ + goto fallback_fdinfo; + + /* The parent can't do name_to_handle_at() but the directory we are + * interested in can? If so, it must be a mount point. */ + return 1; + } + + /* The parent can do name_to_handle_at() but the directory we are interested in can't? If + * so, it must be a mount point. */ + if (nosupp) + return 1; + + /* If the file handle for the directory we are interested in and its parent are identical, + * we assume this is the root directory, which is a mount point. */ + if (file_handle_equal(h_parent, h)) + return 1; + + return mount_id != mount_id_parent; + +fallback_fdinfo: + r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id); + if (r < 0) + return r; + + if (fd_is_self) + r = fd_fdinfo_mnt_id(fd, "..", 0, &mount_id_parent); /* can't work for non-directories 😢 */ + else + r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent); + if (r < 0) + return r; + + if (mount_id != mount_id_parent) + return 1; + + /* Hmm, so, the mount ids are the same. This leaves one special case though for the root file + * system. For that, let's see if the parent directory has the same inode as we are interested + * in. */ + + struct stat a, b; + + /* yay for fstatat() taking a different set of flags than the other _at() above */ + if (fstatat(fd, filename, &a, at_flags_normalize_nofollow(flags)) < 0) + return -errno; + + if (fd_is_self) + r = fstatat(fd, "..", &b, 0); + else + r = fstatat(fd, "", &b, AT_EMPTY_PATH); + if (r < 0) + return -errno; + + /* A directory with same device and inode as its parent must be the root directory. Otherwise + * not a mount point. + * + * NB: we avoid inode_same_at() here because it internally attempts name_to_handle_at_try_fid() first, + * which is redundant. */ + return stat_inode_same(&a, &b); +} + +/* flags can be AT_SYMLINK_FOLLOW or 0 */ +int path_is_mount_point_full(const char *path, const char *root, int flags) { + _cleanup_close_ int dfd = -EBADF; + _cleanup_free_ char *fn = NULL; + + assert(path); + assert((flags & ~AT_SYMLINK_FOLLOW) == 0); + + if (path_equal(path, "/")) + return 1; + + /* we need to resolve symlinks manually, we can't just rely on is_mount_point_at() to do that for us; + * if we have a structure like /bin -> /usr/bin/ and /usr is a mount point, then the parent that we + * look at needs to be /usr, not /. */ + dfd = chase_and_open_parent(path, root, + CHASE_TRAIL_SLASH|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : CHASE_NOFOLLOW), + &fn); + if (dfd < 0) + return dfd; + + return is_mount_point_at(dfd, fn, flags); +} + +int path_get_mnt_id_at_fallback(int dir_fd, const char *path, int *ret) { + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(ret); + + r = name_to_handle_at_loop(dir_fd, path, NULL, ret, isempty(path) ? AT_EMPTY_PATH : 0); + if (r >= 0 || is_name_to_handle_at_fatal_error(r)) + return r; + + return fd_fdinfo_mnt_id(dir_fd, path, isempty(path) ? AT_EMPTY_PATH : 0, ret); +} + +int path_get_mnt_id_at(int dir_fd, const char *path, int *ret) { + struct statx sx; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(ret); + + if (statx(dir_fd, + strempty(path), + (isempty(path) ? AT_EMPTY_PATH : AT_SYMLINK_NOFOLLOW) | + AT_NO_AUTOMOUNT | /* don't trigger automounts, mnt_id is a local concept */ + AT_STATX_DONT_SYNC, /* don't go to the network, mnt_id is a local concept */ + STATX_MNT_ID, + &sx) < 0) + return -errno; + + if (FLAGS_SET(sx.stx_mask, STATX_MNT_ID)) { + *ret = sx.stx_mnt_id; + return 0; + } + + return path_get_mnt_id_at_fallback(dir_fd, path, ret); +} + +bool fstype_is_network(const char *fstype) { + const char *x; + + x = startswith(fstype, "fuse."); + if (x) + fstype = x; + + if (nulstr_contains(filesystem_sets[FILESYSTEM_SET_NETWORK].value, fstype)) + return true; + + /* Filesystems not present in the internal database */ + return STR_IN_SET(fstype, + "davfs", + "glusterfs", + "lustre", + "sshfs"); +} + +bool fstype_needs_quota(const char *fstype) { + /* 1. quotacheck needs to be run for some filesystems after they are mounted + * if the filesystem was not unmounted cleanly. + * 2. You may need to run quotaon to enable quota usage tracking and/or + * enforcement. + * ext2 - needs 1) and 2) + * ext3 - needs 2) if configured using usrjquota/grpjquota mount options + * ext4 - needs 1) if created without journal, needs 2) if created without QUOTA + * filesystem feature + * reiserfs - needs 2). + * jfs - needs 2) + * f2fs - needs 2) if configured using usrjquota/grpjquota/prjjquota mount options + * xfs - nothing needed + * gfs2 - nothing needed + * ocfs2 - nothing needed + * btrfs - nothing needed + * for reference see filesystem and quota manpages */ + return STR_IN_SET(fstype, + "ext2", + "ext3", + "ext4", + "reiserfs", + "jfs", + "f2fs"); +} + +bool fstype_is_api_vfs(const char *fstype) { + assert(fstype); + + const FilesystemSet *fs; + FOREACH_ARGUMENT(fs, + filesystem_sets + FILESYSTEM_SET_BASIC_API, + filesystem_sets + FILESYSTEM_SET_AUXILIARY_API, + filesystem_sets + FILESYSTEM_SET_PRIVILEGED_API, + filesystem_sets + FILESYSTEM_SET_TEMPORARY) + if (nulstr_contains(fs->value, fstype)) + return true; + + /* Filesystems not present in the internal database */ + return STR_IN_SET(fstype, + "autofs", + "cpuset", + "devtmpfs"); +} + +bool fstype_is_blockdev_backed(const char *fstype) { + const char *x; + + x = startswith(fstype, "fuse."); + if (x) + fstype = x; + + return !streq(fstype, "9p") && !fstype_is_network(fstype) && !fstype_is_api_vfs(fstype); +} + +bool fstype_is_ro(const char *fstype) { + /* All Linux file systems that are necessarily read-only */ + return STR_IN_SET(fstype, + "DM_verity_hash", + "cramfs", + "erofs", + "iso9660", + "squashfs"); +} + +bool fstype_can_discard(const char *fstype) { + assert(fstype); + + /* Use a curated list as first check, to avoid calling fsopen() which might load kmods, which might + * not be allowed in our MAC context. */ + if (STR_IN_SET(fstype, "btrfs", "f2fs", "ext4", "vfat", "xfs")) + return true; + + /* On new kernels we can just ask the kernel */ + return mount_option_supported(fstype, "discard", NULL) > 0; +} + +const char* fstype_norecovery_option(const char *fstype) { + int r; + + assert(fstype); + + /* Use a curated list as first check, to avoid calling fsopen() which might load kmods, which might + * not be allowed in our MAC context. */ + if (STR_IN_SET(fstype, "ext3", "ext4", "xfs")) + return "norecovery"; + + /* btrfs dropped support for the "norecovery" option in 6.8 + * (https://github.com/torvalds/linux/commit/a1912f712188291f9d7d434fba155461f1ebef66) and replaced + * it with rescue=nologreplay, so we check for the new name first and fall back to checking for the + * old name if the new name doesn't work. */ + if (streq(fstype, "btrfs")) { + r = mount_option_supported(fstype, "rescue=nologreplay", NULL); + if (r == -EAGAIN) { + log_debug_errno(r, "Failed to check for btrfs 'rescue=nologreplay' option, assuming old kernel with 'norecovery': %m"); + return "norecovery"; + } + if (r < 0) + log_debug_errno(r, "Failed to check for btrfs 'rescue=nologreplay' option, assuming it is not supported: %m"); + if (r > 0) + return "rescue=nologreplay"; + } + + /* On new kernels we can just ask the kernel */ + return mount_option_supported(fstype, "norecovery", NULL) > 0 ? "norecovery" : NULL; +} + +bool fstype_can_fmask_dmask(const char *fstype) { + assert(fstype); + + /* Use a curated list as first check, to avoid calling fsopen() which might load kmods, which might + * not be allowed in our MAC context. If we don't know ourselves, on new kernels we can just ask the + * kernel. */ + return streq(fstype, "vfat") || (mount_option_supported(fstype, "fmask", "0177") > 0 && mount_option_supported(fstype, "dmask", "0077") > 0); +} + +bool fstype_can_uid_gid(const char *fstype) { + /* All file systems that have a uid=/gid= mount option that fixates the owners of all files and + * directories, current and future. Note that this does *not* ask the kernel via + * mount_option_supported() here because the uid=/gid= setting of various file systems mean different + * things: some apply it only to the root dir inode, others to all inodes in the file system. Thus we + * maintain the curated list below. 😢 */ + + return STR_IN_SET(fstype, + "adfs", + "exfat", + "fat", + "hfs", + "hpfs", + "iso9660", + "msdos", + "ntfs", + "vfat"); +} + +int dev_is_devtmpfs(void) { + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + int mount_id, r; + char *e; + + r = path_get_mnt_id("/dev", &mount_id); + if (r < 0) + return r; + + r = fopen_unlocked("/proc/self/mountinfo", "re", &proc_self_mountinfo); + if (r == -ENOENT) + return proc_mounted() > 0 ? -ENOENT : -ENOSYS; + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *line = NULL; + int mid; + + r = read_line(proc_self_mountinfo, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) + break; + + if (sscanf(line, "%i", &mid) != 1) + continue; + + if (mid != mount_id) + continue; + + e = strstrafter(line, " - "); + if (!e) + continue; + + /* accept any name that starts with the currently expected type */ + if (startswith(e, "devtmpfs")) + return true; + } + + return false; +} + +static int mount_fd( + const char *source, + int target_fd, + const char *filesystemtype, + unsigned long mountflags, + const void *data) { + + assert(target_fd >= 0); + + if (mount(source, FORMAT_PROC_FD_PATH(target_fd), filesystemtype, mountflags, data) < 0) { + if (errno != ENOENT) + return -errno; + + /* ENOENT can mean two things: either that the source is missing, or that /proc/ isn't + * mounted. Check for the latter to generate better error messages. */ + if (proc_mounted() == 0) + return -ENOSYS; + + return -ENOENT; + } + + return 0; +} + +int mount_nofollow( + const char *source, + const char *target, + const char *filesystemtype, + unsigned long mountflags, + const void *data) { + + _cleanup_close_ int fd = -EBADF; + + assert(target); + + /* In almost all cases we want to manipulate the mount table without following symlinks, hence + * mount_nofollow() is usually the way to go. The only exceptions are environments where /proc/ is + * not available yet, since we need /proc/self/fd/ for this logic to work. i.e. during the early + * initialization of namespacing/container stuff where /proc is not yet mounted (and maybe even the + * fs to mount) we can only use traditional mount() directly. + * + * Note that this disables following only for the final component of the target, i.e symlinks within + * the path of the target are honoured, as are symlinks in the source path everywhere. */ + + fd = open(target, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return mount_fd(source, fd, filesystemtype, mountflags, data); +} + +const char* mount_propagation_flag_to_string(unsigned long flags) { + + switch (flags & (MS_SHARED|MS_SLAVE|MS_PRIVATE)) { + case 0: + return ""; + case MS_SHARED: + return "shared"; + case MS_SLAVE: + return "slave"; + case MS_PRIVATE: + return "private"; + } + + return NULL; +} + +int mount_propagation_flag_from_string(const char *name, unsigned long *ret) { + + if (isempty(name)) + *ret = 0; + else if (streq(name, "shared")) + *ret = MS_SHARED; + else if (streq(name, "slave")) + *ret = MS_SLAVE; + else if (streq(name, "private")) + *ret = MS_PRIVATE; + else + return -EINVAL; + return 0; +} + +bool mount_propagation_flag_is_valid(unsigned long flag) { + return IN_SET(flag, 0, MS_SHARED, MS_PRIVATE, MS_SLAVE); +} + +bool mount_new_api_supported(void) { + static int cache = -1; + int r; + + if (cache >= 0) + return cache; + + /* This is the newer API among the ones we use, so use it as boundary */ + r = RET_NERRNO(mount_setattr(-EBADF, NULL, 0, NULL, 0)); + if (r == 0 || ERRNO_IS_NOT_SUPPORTED(r)) /* This should return an error if it is working properly */ + return (cache = false); + + return (cache = true); +} + +unsigned long ms_nosymfollow_supported(void) { + _cleanup_close_ int fsfd = -EBADF, mntfd = -EBADF; + static int cache = -1; + + /* Returns MS_NOSYMFOLLOW if it is supported, zero otherwise. */ + + if (cache >= 0) + return cache ? MS_NOSYMFOLLOW : 0; + + if (!mount_new_api_supported()) + goto not_supported; + + /* Checks if MS_NOSYMFOLLOW is supported (which was added in 5.10). We use the new mount API's + * mount_setattr() call for that, which was added in 5.12, which is close enough. */ + + fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC); + if (fsfd < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) + goto not_supported; + + log_debug_errno(errno, "Failed to open superblock context for tmpfs: %m"); + return 0; + } + + if (fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) + goto not_supported; + + log_debug_errno(errno, "Failed to create tmpfs superblock: %m"); + return 0; + } + + mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0); + if (mntfd < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) + goto not_supported; + + log_debug_errno(errno, "Failed to turn superblock fd into mount fd: %m"); + return 0; + } + + if (mount_setattr(mntfd, "", AT_EMPTY_PATH|AT_RECURSIVE, + &(struct mount_attr) { + .attr_set = MOUNT_ATTR_NOSYMFOLLOW, + }, sizeof(struct mount_attr)) < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) + goto not_supported; + + log_debug_errno(errno, "Failed to set MOUNT_ATTR_NOSYMFOLLOW mount attribute: %m"); + return 0; + } + + cache = true; + return MS_NOSYMFOLLOW; + +not_supported: + cache = false; + return 0; +} + +int mount_option_supported(const char *fstype, const char *key, const char *value) { + _cleanup_close_ int fd = -EBADF; + int r; + + /* Checks if the specified file system supports a mount option. Returns > 0 if it supports it, == 0 if + * it does not. Return -EAGAIN if we can't determine it. And any other error otherwise. */ + + assert(fstype); + assert(key); + + fd = fsopen(fstype, FSOPEN_CLOEXEC); + if (fd < 0) + return log_debug_errno(errno, "Failed to open superblock context for '%s': %m", fstype); + + /* Various file systems support fs context only in recent kernels (e.g. btrfs). For older kernels + * fsconfig() with FSCONFIG_SET_STRING/FSCONFIG_SET_FLAG never fail. Which sucks, because we want to + * use it for testing support, after all. Let's hence do a check if the file system got converted yet + * first. */ + if (fsconfig(fd, FSCONFIG_SET_FD, "adefinitelynotexistingmountoption", NULL, fd) < 0) { + /* If FSCONFIG_SET_FD is not supported for the fs, then the file system was not converted to + * the new mount API yet. If it returns EINVAL the mount option doesn't exist, but the fstype + * is converted. */ + if (errno == EOPNOTSUPP) + return -EAGAIN; /* fs not converted to new mount API → don't know */ + if (errno != EINVAL) + return log_debug_errno(errno, "Failed to check if file system '%s' has been converted to new mount API: %m", fstype); + + /* So FSCONFIG_SET_FD worked, but the option didn't exist (we got EINVAL), this means the fs + * is converted. Let's now ask the actual question we wonder about. */ + } else + return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "FSCONFIG_SET_FD worked unexpectedly for '%s', whoa!", fstype); + + if (value) + r = fsconfig(fd, FSCONFIG_SET_STRING, key, value, 0); + else + r = fsconfig(fd, FSCONFIG_SET_FLAG, key, NULL, 0); + if (r < 0) { + if (errno == EINVAL) + return false; /* EINVAL means option not supported. */ + + return log_debug_errno(errno, "Failed to set '%s%s%s' on '%s' superblock context: %m", + key, value ? "=" : "", strempty(value), fstype); + } + + return true; /* works! */ +} + +bool path_below_api_vfs(const char *p) { + assert(p); + + /* API VFS are either directly mounted on any of these three paths, or below it. */ + return PATH_STARTSWITH_SET(p, "/dev", "/sys", "/proc"); +} diff --git a/src/libnm-systemd-shared/src/basic/mountpoint-util.h b/src/libnm-systemd-shared/src/basic/mountpoint-util.h new file mode 100644 index 0000000000..43e4758143 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/mountpoint-util.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include +#include +#include + +/* The limit used for /dev itself. 4MB should be enough since device nodes and symlinks don't + * consume any space and udev isn't supposed to create regular file either. There's no limit on the + * max number of inodes since such limit is hard to guess especially on large storage array + * systems. */ +#define TMPFS_LIMITS_DEV ",size=4m" + +/* The limit used for /dev in private namespaces. 4MB for contents of regular files. The number of + * inodes should be relatively low in private namespaces but for now use a 64k limit. */ +#define TMPFS_LIMITS_PRIVATE_DEV ",size=4m,nr_inodes=64k" + +/* Very little, if any use expected */ +#define TMPFS_LIMITS_EMPTY_OR_ALMOST ",size=4m,nr_inodes=1k" +#define TMPFS_LIMITS_SYS TMPFS_LIMITS_EMPTY_OR_ALMOST +#define TMPFS_LIMITS_SYS_FS_CGROUP TMPFS_LIMITS_EMPTY_OR_ALMOST + +/* On an extremely small device with only 256MB of RAM, 20% of RAM should be enough for the re-execution of + * PID1 because 16MB of free space is required. */ +#define TMPFS_LIMITS_RUN ",size=20%,nr_inodes=800k" + +/* The limit used for various nested tmpfs mounts, in particular for guests started by systemd-nspawn. + * 10% of RAM (using 16GB of RAM as a baseline) translates to 400k inodes (assuming 4k each) and 25% + * translates to 1M inodes. + * (On the host, /tmp is configured through a .mount unit file.) */ +#define NESTED_TMPFS_LIMITS ",size=10%,nr_inodes=400k" + +/* More space for volatile root and /var */ +#define TMPFS_LIMITS_VAR ",size=25%,nr_inodes=1m" +#define TMPFS_LIMITS_ROOTFS TMPFS_LIMITS_VAR +#define TMPFS_LIMITS_VOLATILE_STATE TMPFS_LIMITS_VAR + +bool is_name_to_handle_at_fatal_error(int err); + +int name_to_handle_at_loop(int fd, const char *path, struct file_handle **ret_handle, int *ret_mnt_id, int flags); +int name_to_handle_at_try_fid(int fd, const char *path, struct file_handle **ret_handle, int *ret_mnt_id, int flags); + +bool file_handle_equal(const struct file_handle *a, const struct file_handle *b); + +int path_get_mnt_id_at_fallback(int dir_fd, const char *path, int *ret); +int path_get_mnt_id_at(int dir_fd, const char *path, int *ret); +static inline int path_get_mnt_id(const char *path, int *ret) { + return path_get_mnt_id_at(AT_FDCWD, path, ret); +} + +int is_mount_point_at(int fd, const char *filename, int flags); +int path_is_mount_point_full(const char *path, const char *root, int flags); +static inline int path_is_mount_point(const char *path) { + return path_is_mount_point_full(path, NULL, 0); +} + +bool fstype_is_network(const char *fstype); +bool fstype_needs_quota(const char *fstype); +bool fstype_is_api_vfs(const char *fstype); +bool fstype_is_blockdev_backed(const char *fstype); +bool fstype_is_ro(const char *fsype); +bool fstype_can_discard(const char *fstype); +bool fstype_can_uid_gid(const char *fstype); +bool fstype_can_fmask_dmask(const char *fstype); + +const char* fstype_norecovery_option(const char *fstype); + +int dev_is_devtmpfs(void); + +int mount_nofollow( + const char *source, + const char *target, + const char *filesystemtype, + unsigned long mountflags, + const void *data); + +const char* mount_propagation_flag_to_string(unsigned long flags); +int mount_propagation_flag_from_string(const char *name, unsigned long *ret); +bool mount_propagation_flag_is_valid(unsigned long flag); + +bool mount_new_api_supported(void); +unsigned long ms_nosymfollow_supported(void); + +int mount_option_supported(const char *fstype, const char *key, const char *value); + +bool path_below_api_vfs(const char *p); diff --git a/src/libnm-systemd-shared/src/basic/namespace-util.h b/src/libnm-systemd-shared/src/basic/namespace-util.h index 3d40a515e7..d7ac8156f9 100644 --- a/src/libnm-systemd-shared/src/basic/namespace-util.h +++ b/src/libnm-systemd-shared/src/basic/namespace-util.h @@ -28,6 +28,8 @@ extern const struct namespace_info { NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag); +bool namespace_type_supported(NamespaceType type); + int pidref_namespace_open_by_type(const PidRef *pidref, NamespaceType type); int namespace_open_by_type(NamespaceType type); @@ -86,7 +88,8 @@ static inline bool userns_shift_range_valid(uid_t shift, uid_t range) { 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_acquire(const char *uid_map, const char *gid_map, bool setgroups_deny); +int userns_acquire_self_root(void); 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); diff --git a/src/libnm-systemd-shared/src/basic/ordered-set.h b/src/libnm-systemd-shared/src/basic/ordered-set.h index e73da20573..b7113ad918 100644 --- a/src/libnm-systemd-shared/src/basic/ordered-set.h +++ b/src/libnm-systemd-shared/src/basic/ordered-set.h @@ -22,18 +22,10 @@ static inline void ordered_set_clear(OrderedSet *s) { return ordered_hashmap_clear((OrderedHashmap*) s); } -static inline void ordered_set_clear_free(OrderedSet *s) { - return ordered_hashmap_clear_free((OrderedHashmap*) s); -} - static inline OrderedSet* ordered_set_free(OrderedSet *s) { return (OrderedSet*) ordered_hashmap_free((OrderedHashmap*) s); } -static inline OrderedSet* ordered_set_free_free(OrderedSet *s) { - return (OrderedSet*) ordered_hashmap_free_free((OrderedHashmap*) s); -} - static inline int ordered_set_contains(OrderedSet *s, const void *p) { return ordered_hashmap_contains((OrderedHashmap*) s, p); } @@ -91,19 +83,6 @@ void ordered_set_print(FILE *f, const char *field, OrderedSet *s); #define ORDERED_SET_FOREACH(e, s) \ _ORDERED_SET_FOREACH(e, s, UNIQ_T(i, UNIQ)) -#define ordered_set_clear_with_destructor(s, f) \ - ({ \ - OrderedSet *_s = (s); \ - void *_item; \ - while ((_item = ordered_set_steal_first(_s))) \ - f(_item); \ - _s; \ - }) -#define ordered_set_free_with_destructor(s, f) \ - ordered_set_free(ordered_set_clear_with_destructor(s, f)) - DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free_free); #define _cleanup_ordered_set_free_ _cleanup_(ordered_set_freep) -#define _cleanup_ordered_set_free_free_ _cleanup_(ordered_set_free_freep) diff --git a/src/libnm-systemd-shared/src/basic/parse-util.c b/src/libnm-systemd-shared/src/basic/parse-util.c index faa5344921..a10990dbe0 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.c +++ b/src/libnm-systemd-shared/src/basic/parse-util.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include "errno-list.h" #include "extract-word.h" #include "locale-util.h" +#include "log.h" #include "macro.h" #include "missing_network.h" #include "parse-util.h" @@ -527,7 +529,7 @@ int safe_atollu_full(const char *s, unsigned base, unsigned long long *ret_llu) return 0; } -int safe_atolli(const char *s, long long int *ret_lli) { +int safe_atolli(const char *s, long long *ret_lli) { unsigned base = 0; char *x = NULL; long long l; @@ -777,18 +779,14 @@ int parse_loadavg_fixed_point(const char *s, loadavg_t *ret) { /* Limitations are described in https://www.netfilter.org/projects/nftables/manpage.html and * https://bugzilla.netfilter.org/show_bug.cgi?id=1175 */ bool nft_identifier_valid(const char *id) { - if (!id) + if (isempty(id)) return false; - size_t len = strlen(id); - if (len == 0 || len > 31) + if (strlen(id) >= NFT_NAME_MAXLEN) return false; if (!ascii_isalpha(id[0])) return false; - for (size_t i = 1; i < len; i++) - if (!ascii_isalpha(id[i]) && !ascii_isdigit(id[i]) && !IN_SET(id[i], '/', '\\', '_', '.')) - return false; - return true; + return in_charset(id + 1, ALPHANUMERICAL "/\\_."); } diff --git a/src/libnm-systemd-shared/src/basic/parse-util.h b/src/libnm-systemd-shared/src/basic/parse-util.h index a47c8c7935..0af407b335 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.h +++ b/src/libnm-systemd-shared/src/basic/parse-util.h @@ -41,7 +41,7 @@ static inline int safe_atou(const char *s, unsigned *ret_u) { int safe_atou_bounded(const char *s, unsigned min, unsigned max, unsigned *ret); int safe_atoi(const char *s, int *ret_i); -int safe_atolli(const char *s, long long int *ret_i); +int safe_atolli(const char *s, long long *ret_i); int safe_atou8_full(const char *s, unsigned base, uint8_t *ret); @@ -87,8 +87,8 @@ static inline int safe_atou64(const char *s, uint64_t *ret_u) { } static inline int safe_atoi64(const char *s, int64_t *ret_i) { - assert_cc(sizeof(int64_t) == sizeof(long long int)); - return safe_atolli(s, (long long int*) ret_i); + assert_cc(sizeof(int64_t) == sizeof(long long)); + return safe_atolli(s, (long long*) ret_i); } static inline int safe_atoux64(const char *s, uint64_t *ret) { @@ -101,8 +101,8 @@ static inline int safe_atolu_full(const char *s, unsigned base, unsigned long *r assert_cc(sizeof(unsigned long) == sizeof(unsigned)); return safe_atou_full(s, base, (unsigned*) ret_u); } -static inline int safe_atoli(const char *s, long int *ret_u) { - assert_cc(sizeof(long int) == sizeof(int)); +static inline int safe_atoli(const char *s, long *ret_u) { + assert_cc(sizeof(long) == sizeof(int)); return safe_atoi(s, (int*) ret_u); } #else @@ -110,9 +110,9 @@ static inline int safe_atolu_full(const char *s, unsigned base, unsigned long *r assert_cc(sizeof(unsigned long) == sizeof(unsigned long long)); return safe_atollu_full(s, base, (unsigned long long*) ret_u); } -static inline int safe_atoli(const char *s, long int *ret_u) { - assert_cc(sizeof(long int) == sizeof(long long int)); - return safe_atolli(s, (long long int*) ret_u); +static inline int safe_atoli(const char *s, long *ret_u) { + assert_cc(sizeof(long) == sizeof(long long)); + return safe_atolli(s, (long long*) ret_u); } #endif diff --git a/src/libnm-systemd-shared/src/basic/path-util.c b/src/libnm-systemd-shared/src/basic/path-util.c index 78ba10ed80..dbde364eff 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.c +++ b/src/libnm-systemd-shared/src/basic/path-util.c @@ -613,37 +613,9 @@ char* path_extend_internal(char **x, ...) { return nx; } -static int check_x_access(const char *path, int *ret_fd) { +int open_and_check_executable(const char *name, const char *root, char **ret_path, int *ret_fd) { _cleanup_close_ int fd = -EBADF; - int r; - - /* We need to use O_PATH because there may be executables for which we have only exec - * permissions, but not read (usually suid executables). */ - fd = open(path, O_PATH|O_CLOEXEC); - if (fd < 0) - return -errno; - - r = fd_verify_regular(fd); - if (r < 0) - return r; - - r = access_fd(fd, X_OK); - if (r == -ENOSYS) { - /* /proc is not mounted. Fallback to access(). */ - if (access(path, X_OK) < 0) - return -errno; - } else if (r < 0) - return r; - - if (ret_fd) - *ret_fd = TAKE_FD(fd); - - return 0; -} - -static int find_executable_impl(const char *name, const char *root, char **ret_filename, int *ret_fd) { - _cleanup_close_ int fd = -EBADF; - _cleanup_free_ char *path_name = NULL; + _cleanup_free_ char *resolved = NULL; int r; assert(name); @@ -654,23 +626,40 @@ static int find_executable_impl(const char *name, const char *root, char **ret_f * needed to avoid unforeseen regression or other complicated changes. */ if (root) { /* prefix root to name in case full paths are not specified */ - r = chase(name, root, CHASE_PREFIX_ROOT, &path_name, /* ret_fd= */ NULL); + r = chase(name, root, CHASE_PREFIX_ROOT, &resolved, &fd); if (r < 0) return r; - name = path_name; + name = resolved; + } else { + /* We need to use O_PATH because there may be executables for which we have only exec permissions, + * but not read (usually suid executables). */ + fd = open(name, O_PATH|O_CLOEXEC); + if (fd < 0) + return -errno; } - r = check_x_access(name, ret_fd ? &fd : NULL); + r = fd_verify_regular(fd); if (r < 0) return r; - if (ret_filename) { - r = path_make_absolute_cwd(name, ret_filename); - if (r < 0) - return r; + r = access_fd(fd, X_OK); + if (r == -ENOSYS) + /* /proc/ is not mounted. Fall back to access(). */ + r = RET_NERRNO(access(name, X_OK)); + if (r < 0) + return r; - path_simplify(*ret_filename); + if (ret_path) { + if (resolved) + *ret_path = TAKE_PTR(resolved); + else { + r = path_make_absolute_cwd(name, ret_path); + if (r < 0) + return r; + + path_simplify(*ret_path); + } } if (ret_fd) @@ -692,7 +681,7 @@ int find_executable_full( assert(name); if (is_path(name)) - return find_executable_impl(name, root, ret_filename, ret_fd); + return open_and_check_executable(name, root, ret_filename, ret_fd); if (exec_search_path) { STRV_FOREACH(element, exec_search_path) { @@ -707,7 +696,7 @@ int find_executable_full( if (!full_path) return -ENOMEM; - r = find_executable_impl(full_path, root, ret_filename, ret_fd); + r = open_and_check_executable(full_path, root, ret_filename, ret_fd); if (r >= 0) return 0; if (r != -EACCES) @@ -743,7 +732,7 @@ int find_executable_full( if (!path_extend(&element, name)) return -ENOMEM; - r = find_executable_impl(element, root, ret_filename, ret_fd); + r = open_and_check_executable(element, root, ret_filename, ret_fd); if (r >= 0) /* Found it! */ return 0; /* PATH entries which we don't have access to are ignored, as per tradition. */ diff --git a/src/libnm-systemd-shared/src/basic/path-util.h b/src/libnm-systemd-shared/src/basic/path-util.h index dff5a3a549..436bd5d892 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.h +++ b/src/libnm-systemd-shared/src/basic/path-util.h @@ -115,6 +115,7 @@ 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 open_and_check_executable(const char *name, const char *root, char **ret_path, int *ret_fd); int find_executable_full( const char *name, const char *root, diff --git a/src/libnm-systemd-shared/src/basic/pidfd-util.c b/src/libnm-systemd-shared/src/basic/pidfd-util.c index 36fe224324..52a371ba1a 100644 --- a/src/libnm-systemd-shared/src/basic/pidfd-util.c +++ b/src/libnm-systemd-shared/src/basic/pidfd-util.c @@ -1,15 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "log.h" #include "macro.h" #include "memory-util.h" +#include "missing_fs.h" #include "missing_magic.h" -#include "missing_threads.h" +#include "mountpoint-util.h" #include "parse-util.h" #include "path-util.h" #include "pidfd-util.h" @@ -86,26 +89,21 @@ static int pidfd_get_info(int fd, struct pidfd_info *info) { 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; + _cleanup_free_ char *p = NULL; int r; assert(fd >= 0); xsprintf(path, "/proc/self/fdinfo/%i", fd); - r = read_full_virtual_file(path, &fdinfo, NULL); + r = get_proc_field(path, "Pid", &p); if (r == -ENOENT) - return proc_fd_enoent_errno(); + return -EBADF; + if (r == -ENODATA) /* not a pidfd? */ + return -ENOTTY; 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")) @@ -226,6 +224,7 @@ int pidfd_get_cgroupid(int fd, uint64_t *ret) { } int pidfd_get_inode_id(int fd, uint64_t *ret) { + static bool file_handle_supported = true; int r; assert(fd >= 0); @@ -236,6 +235,30 @@ int pidfd_get_inode_id(int fd, uint64_t *ret) { if (r == 0) return -EOPNOTSUPP; + if (file_handle_supported) { + union { + struct file_handle file_handle; + uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(uint64_t)]; + } fh = { + .file_handle.handle_bytes = sizeof(uint64_t), + .file_handle.handle_type = FILEID_KERNFS, + }; + int mnt_id; + + r = RET_NERRNO(name_to_handle_at(fd, "", &fh.file_handle, &mnt_id, AT_EMPTY_PATH)); + if (r >= 0) { + if (ret) + *ret = *(uint64_t*) fh.file_handle.f_handle; + return 0; + } + assert(r != -EOVERFLOW); + if (is_name_to_handle_at_fatal_error(r)) + return r; + + file_handle_supported = false; + } + +#if SIZEOF_INO_T == 8 struct stat st; if (fstat(fd, &st) < 0) return -errno; @@ -243,6 +266,16 @@ int pidfd_get_inode_id(int fd, uint64_t *ret) { if (ret) *ret = (uint64_t) st.st_ino; return 0; + +#elif SIZEOF_INO_T == 4 + /* On 32-bit systems (where sizeof(ino_t) == 4), the inode id returned by fstat() cannot be used to + * reliably identify the process, nor can we communicate the origin of the id with the clients. + * Hence let's just refuse to acquire pidfdid through fstat() here. All clients shall also insist on + * the 64-bit id from name_to_handle_at(). */ + return -EOPNOTSUPP; +#else +# error Unsupported ino_t size +#endif } 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 9e8a39ecfb..064b9d851a 100644 --- a/src/libnm-systemd-shared/src/basic/pidref.h +++ b/src/libnm-systemd-shared/src/basic/pidref.h @@ -1,10 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -typedef struct PidRef PidRef; - #include "macro.h" -#include "process-util.h" +#include "memory-util.h" /* 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 @@ -29,22 +27,22 @@ typedef struct PidRef PidRef; * 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. */ +typedef struct PidRef { + pid_t pid; /* > 0 if the PidRef is set, otherwise set to INT_MIN (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 */ -}; +} PidRef; #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 } + * pidref_is_set(). */ +#define PIDREF_AUTOMATIC (const PidRef) { .pid = (pid_t) INT_MIN, .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) */ @@ -70,7 +68,7 @@ bool pidref_equal(PidRef *a, PidRef *b); int pidref_set_pid(PidRef *pidref, pid_t pid); int pidref_set_pidstr(PidRef *pidref, const char *pid); int pidref_set_pidfd(PidRef *pidref, int fd); -int pidref_set_pidfd_take(PidRef *pidref, int fd); /* takes ownership of the passed pidfd on success*/ +int pidref_set_pidfd_take(PidRef *pidref, int fd); /* takes ownership of the passed pidfd on success */ int pidref_set_pidfd_consume(PidRef *pidref, int fd); /* takes ownership of the passed pidfd in both success and failure */ int pidref_set_parent(PidRef *ret); static inline int pidref_set_self(PidRef *pidref) { @@ -108,5 +106,9 @@ int pidref_verify(const PidRef *pidref); #define TAKE_PIDREF(p) TAKE_GENERIC((p), PidRef, PIDREF_NULL) +struct siphash; +void pidref_hash_func(const PidRef *pidref, struct siphash *state); +int pidref_compare_func(const PidRef *a, const PidRef *b); + extern const struct hash_ops pidref_hash_ops; extern const struct hash_ops pidref_hash_ops_free; /* Has destructor call for pidref_free(), i.e. expects heap allocated PidRef as keys */ diff --git a/src/libnm-systemd-shared/src/basic/prioq.c b/src/libnm-systemd-shared/src/basic/prioq.c index b3cf88ecad..b3fec89f76 100644 --- a/src/libnm-systemd-shared/src/basic/prioq.c +++ b/src/libnm-systemd-shared/src/basic/prioq.c @@ -46,6 +46,11 @@ Prioq* prioq_free(Prioq *q) { if (!q) return NULL; + /* Invalidate the index fields of any remaining objects */ + FOREACH_ARRAY(item, q->items, q->n_items) + if (item->idx) + *(item->idx) = PRIOQ_IDX_NULL; + free(q->items); return mfree(q); } @@ -178,6 +183,11 @@ static void remove_item(Prioq *q, struct prioq_item *i) { assert(q); assert(i); + /* Let's invalidate the index pointer stored in the user's object to indicate the item is now removed + * from the priority queue */ + if (i->idx) + *(i->idx) = PRIOQ_IDX_NULL; + l = q->items + q->n_items - 1; if (i == l) @@ -189,6 +199,7 @@ static void remove_item(Prioq *q, struct prioq_item *i) { /* Not last entry, let's replace the last entry with * this one, and reshuffle */ + assert(i >= q->items); k = i - q->items; i->data = l->data; @@ -252,6 +263,7 @@ void prioq_reshuffle(Prioq *q, void *data, unsigned *idx) { if (!i) return; + assert(i >= q->items); k = i - q->items; k = shuffle_down(q, k); shuffle_up(q, k); diff --git a/src/libnm-systemd-shared/src/basic/process-util.c b/src/libnm-systemd-shared/src/basic/process-util.c index fbde9c35e6..945d95d7d6 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.c +++ b/src/libnm-systemd-shared/src/basic/process-util.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #if HAVE_VALGRIND_VALGRIND_H #include @@ -36,13 +37,13 @@ #include "fs-util.h" #include "hostname-util.h" #include "io-util.h" +#include "iovec-util.h" #include "locale-util.h" #include "log.h" #include "macro.h" #include "memory-util.h" #include "missing_sched.h" #include "missing_syscall.h" -#include "missing_threads.h" #include "mountpoint-util.h" #include "namespace-util.h" #include "nulstr-util.h" @@ -53,6 +54,7 @@ #include "raw-clone.h" #include "rlimit-util.h" #include "signal-util.h" +#include "socket-util.h" #include "stat-util.h" #include "stdio-util.h" #include "string-table.h" @@ -505,48 +507,10 @@ int get_process_exe(pid_t pid, char **ret) { return 0; } -static int get_process_id(pid_t pid, const char *field, uid_t *ret) { - _cleanup_fclose_ FILE *f = NULL; - const char *p; +int pid_get_uid(pid_t pid, uid_t *ret) { int r; - assert(field); - assert(ret); - - if (pid < 0) - return -EINVAL; - - p = procfs_file_alloca(pid, "status"); - r = fopen_unlocked(p, "re", &f); - if (r == -ENOENT) - return -ESRCH; - if (r < 0) - return r; - - for (;;) { - _cleanup_free_ char *line = NULL; - char *l; - - r = read_stripped_line(f, LONG_LINE_MAX, &line); - if (r < 0) - return r; - if (r == 0) - break; - - l = startswith(line, field); - if (l) { - l += strspn(l, WHITESPACE); - - l[strcspn(l, WHITESPACE)] = 0; - - return parse_uid(l, ret); - } - } - - return -EIO; -} - -int pid_get_uid(pid_t pid, uid_t *ret) { + assert(pid >= 0); assert(ret); if (pid == 0 || pid == getpid_cached()) { @@ -554,7 +518,14 @@ int pid_get_uid(pid_t pid, uid_t *ret) { return 0; } - return get_process_id(pid, "Uid:", ret); + _cleanup_free_ char *v = NULL; + r = procfs_file_get_field(pid, "status", "Uid", &v); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + return parse_uid(v, ret); } int pidref_get_uid(const PidRef *pid, uid_t *ret) { @@ -587,14 +558,24 @@ int pidref_get_uid(const PidRef *pid, uid_t *ret) { } int get_process_gid(pid_t pid, gid_t *ret) { + int r; + + assert(pid >= 0); + assert(ret); if (pid == 0 || pid == getpid_cached()) { *ret = getgid(); return 0; } - assert_cc(sizeof(uid_t) == sizeof(gid_t)); - return get_process_id(pid, "Gid:", ret); + _cleanup_free_ char *v = NULL; + r = procfs_file_get_field(pid, "status", "Gid", &v); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + return parse_gid(v, ret); } int get_process_cwd(pid_t pid, char **ret) { @@ -860,15 +841,12 @@ int pidref_get_start_time(const PidRef *pid, usec_t *ret) { int get_process_umask(pid_t pid, mode_t *ret) { _cleanup_free_ char *m = NULL; - const char *p; int r; assert(pid >= 0); assert(ret); - p = procfs_file_alloca(pid, "status"); - - r = get_proc_field(p, "Umask", WHITESPACE, &m); + r = procfs_file_get_field(pid, "status", "Umask", &m); if (r == -ENOENT) return -ESRCH; if (r < 0) @@ -877,27 +855,8 @@ int get_process_umask(pid_t pid, mode_t *ret) { return parse_mode(m, ret); } -int wait_for_terminate(pid_t pid, siginfo_t *status) { - siginfo_t dummy; - - assert(pid >= 1); - - if (!status) - status = &dummy; - - for (;;) { - zero(*status); - - if (waitid(P_PID, pid, status, WEXITED) < 0) { - - if (errno == EINTR) - continue; - - return negative_errno(); - } - - return 0; - } +int wait_for_terminate(pid_t pid, siginfo_t *ret) { + return pidref_wait_for_terminate(&PIDREF_MAKE_FROM_PID(pid), ret); } /* @@ -914,24 +873,29 @@ int wait_for_terminate(pid_t pid, siginfo_t *status) { * A warning is emitted if the process terminates abnormally, * and also if it returns non-zero unless check_exit_code is true. */ -int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags) { +int pidref_wait_for_terminate_and_check(const char *name, PidRef *pidref, WaitFlags flags) { + int r; + + if (!pidref_is_set(pidref)) + return -ESRCH; + if (pidref_is_remote(pidref)) + return -EREMOTE; + if (pidref->pid == 1 || pidref_is_self(pidref)) + return -ECHILD; + _cleanup_free_ char *buffer = NULL; - siginfo_t status; - int r, prio; - - assert(pid > 1); - if (!name) { - r = pid_get_comm(pid, &buffer); + r = pidref_get_comm(pidref, &buffer); if (r < 0) - log_debug_errno(r, "Failed to acquire process name of " PID_FMT ", ignoring: %m", pid); + log_debug_errno(r, "Failed to acquire process name of " PID_FMT ", ignoring: %m", pidref->pid); else name = buffer; } - prio = flags & WAIT_LOG_ABNORMAL ? LOG_ERR : LOG_DEBUG; + int prio = flags & WAIT_LOG_ABNORMAL ? LOG_ERR : LOG_DEBUG; - r = wait_for_terminate(pid, &status); + siginfo_t status; + r = pidref_wait_for_terminate(pidref, &status); if (r < 0) return log_full_errno(prio, r, "Failed to wait for %s: %m", strna(name)); @@ -954,6 +918,10 @@ int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags) { return -EPROTO; } +int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags) { + return pidref_wait_for_terminate_and_check(name, &PIDREF_MAKE_FROM_PID(pid), flags); +} + /* * Return values: * @@ -1379,8 +1347,8 @@ void valgrind_summary_hack(void) { if (pid < 0) log_struct_errno( LOG_EMERG, errno, - "MESSAGE_ID=" SD_MESSAGE_VALGRIND_HELPER_FORK_STR, - LOG_MESSAGE( "Failed to fork off valgrind helper: %m")); + LOG_MESSAGE_ID(SD_MESSAGE_VALGRIND_HELPER_FORK_STR), + LOG_MESSAGE("Failed to fork off valgrind helper: %m")); else if (pid == 0) exit(EXIT_SUCCESS); else { @@ -1439,7 +1407,7 @@ pid_t getpid_cached(void) { case CACHED_PID_UNSET: { /* Not initialized yet, then do so now */ pid_t new_pid; - new_pid = raw_getpid(); + new_pid = getpid(); if (!installed) { /* __register_atfork() either returns 0 or -ENOMEM, in its glibc implementation. Since it's @@ -1460,7 +1428,7 @@ pid_t getpid_cached(void) { } case CACHED_PID_BUSY: /* Somebody else is currently initializing */ - return raw_getpid(); + return getpid(); default: /* Properly initialized */ return current_value; @@ -1525,25 +1493,28 @@ static int fork_flags_to_signal(ForkFlags flags) { SIGKILL; } -int safe_fork_full( +int pidref_safe_fork_full( const char *name, const int stdio_fds[3], int except_fds[], size_t n_except_fds, ForkFlags flags, - pid_t *ret_pid) { + PidRef *ret_pid) { pid_t original_pid, pid; sigset_t saved_ss, ss; _unused_ _cleanup_(restore_sigsetp) sigset_t *saved_ssp = NULL; bool block_signals = false, block_all = false, intermediary = false; + _cleanup_close_pair_ int pidref_transport_fds[2] = EBADF_PAIR; int prio, r; + assert(!FLAGS_SET(flags, FORK_WAIT|FORK_FREEZE)); assert(!FLAGS_SET(flags, FORK_DETACH) || - (!ret_pid && (flags & (FORK_WAIT|FORK_DEATHSIG_SIGTERM|FORK_DEATHSIG_SIGINT|FORK_DEATHSIG_SIGKILL)) == 0)); + (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. */ + /* A wrapper around fork(), that does a couple of important initializations in addition to mere + * forking. If provided, ret_pid is initialized in both the parent and the child process, both times + * referencing the child process. Returns == 0 in the child and > 0 in the parent. */ prio = flags & FORK_LOG ? LOG_ERR : LOG_DEBUG; @@ -1586,14 +1557,43 @@ int safe_fork_full( if (!r) { /* Not a reaper process, hence do a double fork() so we are reparented to one */ + if (ret_pid && socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pidref_transport_fds) < 0) + return log_full_errno(prio, errno, "Failed to allocate pidref socket: %m"); + pid = fork(); if (pid < 0) return log_full_errno(prio, errno, "Failed to fork off '%s': %m", strna(name)); if (pid > 0) { log_debug("Successfully forked off intermediary '%s' as PID " PID_FMT ".", strna(name), pid); + + pidref_transport_fds[1] = safe_close(pidref_transport_fds[1]); + + if (pidref_transport_fds[0] >= 0) { + /* Wait for the intermediary child to exit so the caller can be certain the actual child + * process has been reparented by the time this function returns. */ + r = wait_for_terminate_and_check(name, pid, FLAGS_SET(flags, FORK_LOG) ? WAIT_LOG : 0); + if (r < 0) + return log_full_errno(prio, r, "Failed to wait for intermediary process: %m"); + if (r != EXIT_SUCCESS) /* exit status > 0 should be treated as failure, too */ + return -EPROTO; + + int pidfd; + ssize_t n = receive_one_fd_iov( + pidref_transport_fds[0], + &IOVEC_MAKE(&pid, sizeof(pid)), + /* iovlen= */ 1, + /* flags= */ 0, + &pidfd); + if (n < 0) + return log_full_errno(prio, n, "Failed to receive child pidref: %m"); + + *ret_pid = (PidRef) { .pid = pid, .fd = pidfd }; + } + return 1; /* return in the parent */ } + pidref_transport_fds[0] = safe_close(pidref_transport_fds[0]); intermediary = true; } } @@ -1611,8 +1611,30 @@ int safe_fork_full( if (pid > 0) { /* If we are in the intermediary process, exit now */ - if (intermediary) + if (intermediary) { + if (pidref_transport_fds[1] >= 0) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + r = pidref_set_pid(&pidref, pid); + if (r < 0) { + log_full_errno(prio, r, "Failed to open reference to PID "PID_FMT": %m", pid); + _exit(EXIT_FAILURE); + } + + r = send_one_fd_iov( + pidref_transport_fds[1], + pidref.fd, + &IOVEC_MAKE(&pidref.pid, sizeof(pidref.pid)), + /* iovlen= */ 1, + /* flags= */ 0); + if (r < 0) { + log_full_errno(prio, r, "Failed to send child pidref: %m"); + _exit(EXIT_FAILURE); + } + } + _exit(EXIT_SUCCESS); + } /* We are in the parent process */ log_debug("Successfully forked off '%s' as PID " PID_FMT ".", strna(name), pid); @@ -1630,16 +1652,31 @@ int safe_fork_full( return r; if (r != EXIT_SUCCESS) /* exit status > 0 should be treated as failure, too */ return -EPROTO; + + /* If we are in the parent and successfully waited, then the process doesn't exist anymore. */ + if (ret_pid) + *ret_pid = PIDREF_NULL; + + return 1; } - if (ret_pid) - *ret_pid = pid; + if (ret_pid) { + if (FLAGS_SET(flags, FORK_PID_ONLY)) + *ret_pid = PIDREF_MAKE_FROM_PID(pid); + else { + r = pidref_set_pid(ret_pid, pid); + if (r < 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); + } + } return 1; } /* We are in the child process */ + pidref_transport_fds[1] = safe_close(pidref_transport_fds[1]); + /* Restore signal mask manually */ saved_ssp = NULL; @@ -1801,36 +1838,41 @@ int safe_fork_full( if (FLAGS_SET(flags, FORK_FREEZE)) freeze(); - if (ret_pid) - *ret_pid = getpid_cached(); + if (ret_pid) { + if (FLAGS_SET(flags, FORK_PID_ONLY)) + *ret_pid = PIDREF_MAKE_FROM_PID(getpid_cached()); + else { + r = pidref_set_self(ret_pid); + if (r < 0) { + log_full_errno(prio, r, "Failed to acquire PID reference on ourselves: %m"); + _exit(EXIT_FAILURE); + } + } + } return 0; } -int pidref_safe_fork_full( +int safe_fork_full( const char *name, const int stdio_fds[3], int except_fds[], size_t n_except_fds, ForkFlags flags, - PidRef *ret_pid) { + pid_t *ret_pid) { - pid_t pid; - int r, q; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; - r = safe_fork_full(name, stdio_fds, except_fds, n_except_fds, flags, &pid); + /* Getting the detached child process pid without pidfd is racy, so don't allow it if not returning + * a pidref to the caller. */ + assert(!FLAGS_SET(flags, FORK_DETACH) || !ret_pid); + + r = pidref_safe_fork_full(name, stdio_fds, except_fds, n_except_fds, flags|FORK_PID_ONLY, ret_pid ? &pidref : NULL); 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); + *ret_pid = pidref.pid; return r; } @@ -2005,17 +2047,14 @@ _noreturn_ void freeze(void) { int get_process_threads(pid_t pid) { _cleanup_free_ char *t = NULL; - const char *p; int n, r; if (pid < 0) return -EINVAL; - p = procfs_file_alloca(pid, "status"); - - r = get_proc_field(p, "Threads", WHITESPACE, &t); + r = procfs_file_get_field(pid, "status", "Threads", &t); if (r == -ENOENT) - return proc_mounted() == 0 ? -ENOSYS : -ESRCH; + return -ESRCH; if (r < 0) return r; diff --git a/src/libnm-systemd-shared/src/basic/process-util.h b/src/libnm-systemd-shared/src/basic/process-util.h index 58fff2b174..9ae36cf609 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.h +++ b/src/libnm-systemd-shared/src/basic/process-util.h @@ -12,6 +12,8 @@ #include #include "alloc-util.h" +#include "assert-util.h" +#include "fileio.h" #include "format-util.h" #include "macro.h" #include "pidref.h" @@ -22,16 +24,20 @@ pid_t _pid_ = (pid); \ const char *_field_ = (field); \ char *_r_; \ - if (_pid_ == 0) { \ - _r_ = newa(char, STRLEN("/proc/self/") + strlen(_field_) + 1); \ - strcpy(stpcpy(_r_, "/proc/self/"), _field_); \ - } else { \ + if (_pid_ == 0) \ + _r_ = strjoina("/proc/self/", _field_); \ + else { \ + assert(_pid_ > 0); \ _r_ = newa(char, STRLEN("/proc/") + DECIMAL_STR_MAX(pid_t) + 1 + strlen(_field_) + 1); \ sprintf(_r_, "/proc/" PID_FMT "/%s", _pid_, _field_); \ } \ (const char*) _r_; \ }) +static inline int procfs_file_get_field(pid_t pid, const char *name, const char *key, char **ret) { + return get_proc_field(procfs_file_alloca(pid, name), key, ret); +} + typedef enum ProcessCmdlineFlags { PROCESS_CMDLINE_COMM_FALLBACK = 1 << 0, PROCESS_CMDLINE_USE_LOCALE = 1 << 1, @@ -61,7 +67,11 @@ int get_process_umask(pid_t pid, mode_t *ret); int container_get_leader(const char *machine, pid_t *pid); -int wait_for_terminate(pid_t pid, siginfo_t *status); +static inline bool SIGINFO_CODE_IS_DEAD(int code) { + return IN_SET(code, CLD_EXITED, CLD_KILLED, CLD_DUMPED); +} + +int wait_for_terminate(pid_t pid, siginfo_t *ret); typedef enum WaitFlags { WAIT_LOG_ABNORMAL = 1 << 0, @@ -71,7 +81,9 @@ typedef enum WaitFlags { WAIT_LOG = WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS, } WaitFlags; +int pidref_wait_for_terminate_and_check(const char *name, PidRef *pidref, WaitFlags flags); int wait_for_terminate_and_check(const char *name, pid_t pid, WaitFlags flags); + int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout); void sigkill_wait(pid_t pid); @@ -193,20 +205,9 @@ typedef enum ForkFlags { 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 */ + FORK_PID_ONLY = 1 << 23, /* Don't open a pidfd referencing the child process */ } ForkFlags; -int safe_fork_full( - const char *name, - const int stdio_fds[3], - int except_fds[], - size_t n_except_fds, - ForkFlags flags, - pid_t *ret_pid); - -static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) { - return safe_fork_full(name, NULL, NULL, 0, flags, ret_pid); -} - int pidref_safe_fork_full( const char *name, const int stdio_fds[3], @@ -219,6 +220,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 safe_fork_full( + const char *name, + const int stdio_fds[3], + int except_fds[], + size_t n_except_fds, + ForkFlags flags, + pid_t *ret_pid); + +static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) { + return safe_fork_full(name, NULL, NULL, 0, flags, ret_pid); +} + int namespace_fork( const char *outer_name, const char *inner_name, diff --git a/src/libnm-systemd-shared/src/basic/random-util.c b/src/libnm-systemd-shared/src/basic/random-util.c index fec4f81035..c63819e42a 100644 --- a/src/libnm-systemd-shared/src/basic/random-util.c +++ b/src/libnm-systemd-shared/src/basic/random-util.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "alloc-util.h" #include "env-util.h" @@ -19,9 +20,9 @@ #include "fileio.h" #include "io-util.h" #include "iovec-util.h" +#include "log.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" diff --git a/src/libnm-systemd-shared/src/basic/random-util.h b/src/libnm-systemd-shared/src/basic/random-util.h index 0b5ba77190..587ca1c283 100644 --- a/src/libnm-systemd-shared/src/basic/random-util.h +++ b/src/libnm-systemd-shared/src/basic/random-util.h @@ -6,8 +6,10 @@ #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. */ +#include "macro.h" + +void random_bytes(void *p, size_t n) _nonnull_if_nonzero_(1, 2); /* Returns random bytes suitable for most uses, but may be insecure sometimes. */ +int crypto_random_bytes(void *p, size_t n) _nonnull_if_nonzero_(1, 2); /* 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) { @@ -29,6 +31,6 @@ static inline uint32_t random_u32(void) { size_t random_pool_size(void); -int random_write_entropy(int fd, const void *seed, size_t size, bool credit); +int random_write_entropy(int fd, const void *seed, size_t size, bool credit) _nonnull_if_nonzero_(2, 3); uint64_t random_u64_range(uint64_t max); diff --git a/src/libnm-systemd-shared/src/basic/ratelimit.h b/src/libnm-systemd-shared/src/basic/ratelimit.h index 7801ef4270..fd83426375 100644 --- a/src/libnm-systemd-shared/src/basic/ratelimit.h +++ b/src/libnm-systemd-shared/src/basic/ratelimit.h @@ -28,3 +28,55 @@ unsigned ratelimit_num_dropped(const RateLimit *rl); usec_t ratelimit_end(const RateLimit *rl); usec_t ratelimit_left(const RateLimit *rl); + +typedef struct LogRateLimit { + int error; + int level; + RateLimit ratelimit; +} LogRateLimit; + +#define log_ratelimit_internal(_level, _error, _ratelimit, _file, _line, _func, _format, ...) \ +({ \ + int _log_ratelimit_error = (_error); \ + int _log_ratelimit_level = (_level); \ + static LogRateLimit _log_ratelimit = { \ + .ratelimit = (_ratelimit), \ + }; \ + unsigned _num_dropped_errors = ratelimit_num_dropped(&_log_ratelimit.ratelimit); \ + if (_log_ratelimit_error != _log_ratelimit.error || _log_ratelimit_level != _log_ratelimit.level) { \ + ratelimit_reset(&_log_ratelimit.ratelimit); \ + _log_ratelimit.error = _log_ratelimit_error; \ + _log_ratelimit.level = _log_ratelimit_level; \ + } \ + if (log_get_max_level() == LOG_DEBUG || ratelimit_below(&_log_ratelimit.ratelimit)) \ + _log_ratelimit_error = _num_dropped_errors > 0 \ + ? log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format " (Dropped %u similar message(s))", ##__VA_ARGS__, _num_dropped_errors) \ + : log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format, ##__VA_ARGS__); \ + _log_ratelimit_error; \ +}) + +#define log_ratelimit_full_errno(level, error, _ratelimit, format, ...) \ + ({ \ + int _level = (level), _e = (error); \ + _e = (log_get_max_level() >= LOG_PRI(_level)) \ + ? log_ratelimit_internal(_level, _e, _ratelimit, PROJECT_FILE, __LINE__, __func__, format, ##__VA_ARGS__) \ + : -ERRNO_VALUE(_e); \ + _e < 0 ? _e : -ESTRPIPE; \ + }) + +#define log_ratelimit_full(level, _ratelimit, format, ...) \ + log_ratelimit_full_errno(level, 0, _ratelimit, format, ##__VA_ARGS__) + +/* Normal logging */ +#define log_ratelimit_info(...) log_ratelimit_full(LOG_INFO, __VA_ARGS__) +#define log_ratelimit_notice(...) log_ratelimit_full(LOG_NOTICE, __VA_ARGS__) +#define log_ratelimit_warning(...) log_ratelimit_full(LOG_WARNING, __VA_ARGS__) +#define log_ratelimit_error(...) log_ratelimit_full(LOG_ERR, __VA_ARGS__) +#define log_ratelimit_emergency(...) log_ratelimit_full(log_emergency_level(), __VA_ARGS__) + +/* Logging triggered by an errno-like error */ +#define log_ratelimit_info_errno(error, ...) log_ratelimit_full_errno(LOG_INFO, error, __VA_ARGS__) +#define log_ratelimit_notice_errno(error, ...) log_ratelimit_full_errno(LOG_NOTICE, error, __VA_ARGS__) +#define log_ratelimit_warning_errno(error, ...) log_ratelimit_full_errno(LOG_WARNING, error, __VA_ARGS__) +#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__) diff --git a/src/libnm-systemd-shared/src/basic/set.h b/src/libnm-systemd-shared/src/basic/set.h index 618e729744..c7b84e0a54 100644 --- a/src/libnm-systemd-shared/src/basic/set.h +++ b/src/libnm-systemd-shared/src/basic/set.h @@ -12,15 +12,9 @@ Set* _set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); #define set_new(ops) _set_new(ops HASHMAP_DEBUG_SRC_ARGS) static inline Set* set_free(Set *s) { - return (Set*) _hashmap_free(HASHMAP_BASE(s), NULL, NULL); + return (Set*) _hashmap_free(HASHMAP_BASE(s)); } -static inline Set* set_free_free(Set *s) { - return (Set*) _hashmap_free(HASHMAP_BASE(s), free, NULL); -} - -/* no set_free_free_free */ - #define set_copy(s) ((Set*) _hashmap_copy(HASHMAP_BASE(s) HASHMAP_DEBUG_SRC_ARGS)) int _set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); @@ -77,30 +71,13 @@ static inline bool set_iterate(const Set *s, Iterator *i, void **value) { } static inline void set_clear(Set *s) { - _hashmap_clear(HASHMAP_BASE(s), NULL, NULL); + _hashmap_clear(HASHMAP_BASE(s)); } -static inline void set_clear_free(Set *s) { - _hashmap_clear(HASHMAP_BASE(s), free, NULL); -} - -/* no set_clear_free_free */ - static inline void *set_steal_first(Set *s) { return _hashmap_first_key_and_value(HASHMAP_BASE(s), true, NULL); } -#define set_clear_with_destructor(s, f) \ - ({ \ - Set *_s = (s); \ - void *_item; \ - while ((_item = set_steal_first(_s))) \ - f(_item); \ - _s; \ - }) -#define set_free_with_destructor(s, f) \ - set_free(set_clear_with_destructor(s, f)) - /* no set_steal_first_key */ /* no set_first_key */ @@ -114,6 +91,8 @@ static inline char **set_get_strv(Set *s) { return _hashmap_get_strv(HASHMAP_BASE(s)); } +char** set_to_strv(Set **s); + int _set_ensure_put(Set **s, const struct hash_ops *hash_ops, const void *key HASHMAP_DEBUG_PARAMS); #define set_ensure_put(s, hash_ops, key) _set_ensure_put(s, hash_ops, key HASHMAP_DEBUG_SRC_ARGS) @@ -143,10 +122,8 @@ int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags for (; ({ e = set_first(s); assert_se(!e || set_move_one(d, s, e) >= 0); e; }); ) DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free); #define _cleanup_set_free_ _cleanup_(set_freep) -#define _cleanup_set_free_free_ _cleanup_(set_free_freep) int set_strjoin(Set *s, const char *separator, bool wrap_with_separator, char **ret); diff --git a/src/libnm-systemd-shared/src/basic/signal-util.c b/src/libnm-systemd-shared/src/basic/signal-util.c index 32d37e68dd..f5afe34307 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.c +++ b/src/libnm-systemd-shared/src/basic/signal-util.c @@ -2,11 +2,11 @@ #include #include +#include #include "errno-util.h" #include "macro.h" #include "missing_syscall.h" -#include "missing_threads.h" #include "parse-util.h" #include "signal-util.h" #include "stdio-util.h" @@ -280,13 +280,13 @@ void propagate_signal(int sig, siginfo_t *siginfo) { /* To be called from a signal handler. Will raise the same signal again, in our process + in our threads. * - * Note that we use raw_getpid() instead of getpid_cached(). We might have forked with raw_clone() + * Note that we use getpid() instead of getpid_cached(). We might have forked with raw_clone() * earlier (see PID 1), and hence let's go to the raw syscall here. In particular as this is not * performance sensitive code. * * Note that we use kill() rather than raise() as fallback, for similar reasons. */ - p = raw_getpid(); + p = getpid(); if (rt_tgsigqueueinfo(p, gettid(), sig, siginfo) < 0) assert_se(kill(p, sig) >= 0); diff --git a/src/libnm-systemd-shared/src/basic/signal-util.h b/src/libnm-systemd-shared/src/basic/signal-util.h index dc2b9de147..d64d503124 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.h +++ b/src/libnm-systemd-shared/src/basic/signal-util.h @@ -3,6 +3,7 @@ #include +#include "assert-util.h" #include "macro.h" int reset_all_signal_handlers(void); diff --git a/src/libnm-systemd-shared/src/basic/socket-util.c b/src/libnm-systemd-shared/src/basic/socket-util.c index 9190072f4e..61a77370eb 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.c +++ b/src/libnm-systemd-shared/src/basic/socket-util.c @@ -1,10 +1,11 @@ /* 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 #include #include @@ -14,7 +15,6 @@ #include #include #include -#include #include "alloc-util.h" #include "errno-util.h" @@ -28,6 +28,7 @@ #include "parse-util.h" #include "path-util.h" #include "process-util.h" +#include "random-util.h" #include "socket-util.h" #include "string-table.h" #include "string-util.h" @@ -1350,6 +1351,53 @@ void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, return memcpy_safe(buf, CMSG_DATA(cmsg), buf_len); } +size_t sockaddr_ll_len(const struct sockaddr_ll *sa) { + /* Certain hardware address types (e.g Infiniband) do not fit into sll_addr + * (8 bytes) and run over the structure. This function returns the correct size that + * must be passed to kernel. */ + + assert(sa->sll_family == AF_PACKET); + + size_t mac_len = sizeof(sa->sll_addr); + + if (be16toh(sa->sll_hatype) == ARPHRD_ETHER) + mac_len = MAX(mac_len, (size_t) ETH_ALEN); + if (be16toh(sa->sll_hatype) == ARPHRD_INFINIBAND) + mac_len = MAX(mac_len, (size_t) INFINIBAND_ALEN); + + return offsetof(struct sockaddr_ll, sll_addr) + mac_len; +} + +size_t sockaddr_un_len(const struct sockaddr_un *sa) { + /* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */ + + assert(sa->sun_family == AF_UNIX); + + return offsetof(struct sockaddr_un, sun_path) + + (sa->sun_path[0] == 0 ? + 1 + strnlen(sa->sun_path+1, sizeof(sa->sun_path)-1) : + strnlen(sa->sun_path, sizeof(sa->sun_path))+1); +} + +size_t sockaddr_len(const union sockaddr_union *sa) { + switch (sa->sa.sa_family) { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + case AF_UNIX: + return sockaddr_un_len(&sa->un); + case AF_PACKET: + return sockaddr_ll_len(&sa->ll); + case AF_NETLINK: + return sizeof(struct sockaddr_nl); + case AF_VSOCK: + return sizeof(struct sockaddr_vm); + default: + assert_not_reached(); + } +} + int socket_ioctl_fd(void) { int fd; @@ -1445,25 +1493,43 @@ int socket_bind_to_ifname(int fd, const char *ifname) { } int socket_bind_to_ifindex(int fd, int ifindex) { - char ifname[IF_NAMESIZE]; - int r; - assert(fd >= 0); if (ifindex <= 0) /* Drop binding */ return RET_NERRNO(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, NULL, 0)); - r = setsockopt_int(fd, SOL_SOCKET, SO_BINDTOIFINDEX, ifindex); - if (r != -ENOPROTOOPT) - return r; + return setsockopt_int(fd, SOL_SOCKET, SO_BINDTOIFINDEX, ifindex); +} - /* Fall back to SO_BINDTODEVICE on kernels < 5.0 which didn't have SO_BINDTOIFINDEX */ - r = format_ifname(ifindex, ifname); +int socket_autobind(int fd, char **ret_name) { + _cleanup_free_ char *name = NULL; + uint64_t random; + int r; + + /* Generate a random abstract socket name and bind fd to it. This is modeled after the kernel + * "autobind" feature, but uses 64-bit random number internally. */ + + assert(fd >= 0); + assert(ret_name); + + random = random_u64(); + + if (asprintf(&name, "@%" PRIu64, random) < 0) + return -ENOMEM; + + union sockaddr_union sa; + assert_cc(DECIMAL_STR_MAX(uint64_t) < sizeof(sa.un.sun_path)); + + r = sockaddr_un_set_path(&sa.un, name); if (r < 0) return r; - return socket_bind_to_ifname(fd, ifname); + if (bind(fd, &sa.sa, r) < 0) + return -errno; + + *ret_name = TAKE_PTR(name); + return 0; } ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags) { @@ -1800,8 +1866,6 @@ int netlink_socket_get_multicast_groups(int fd, size_t *ret_len, uint32_t **ret_ 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; diff --git a/src/libnm-systemd-shared/src/basic/socket-util.h b/src/libnm-systemd-shared/src/basic/socket-util.h index e21a4427f0..a69277c639 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.h +++ b/src/libnm-systemd-shared/src/basic/socket-util.h @@ -6,12 +6,12 @@ #include #include #include -#include /* linux/vms_sockets.h requires 'struct sockaddr' */ #include #include #include #include #include +#include #include #include @@ -238,62 +238,11 @@ void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, (size) == CMSG_ALIGN(size) ? 1 : -1]; \ } -/* - * Certain hardware address types (e.g Infiniband) do not fit into sll_addr - * (8 bytes) and run over the structure. This macro returns the correct size that - * must be passed to kernel. - */ -#define SOCKADDR_LL_LEN(sa) \ - ({ \ - const struct sockaddr_ll *_sa = &(sa); \ - size_t _mac_len = sizeof(_sa->sll_addr); \ - assert(_sa->sll_family == AF_PACKET); \ - if (be16toh(_sa->sll_hatype) == ARPHRD_ETHER) \ - _mac_len = MAX(_mac_len, (size_t) ETH_ALEN); \ - if (be16toh(_sa->sll_hatype) == ARPHRD_INFINIBAND) \ - _mac_len = MAX(_mac_len, (size_t) INFINIBAND_ALEN); \ - offsetof(struct sockaddr_ll, sll_addr) + _mac_len; \ - }) +size_t sockaddr_ll_len(const struct sockaddr_ll *sa); -/* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */ -#define SOCKADDR_UN_LEN(sa) \ - ({ \ - const struct sockaddr_un *_sa = &(sa); \ - assert(_sa->sun_family == AF_UNIX); \ - offsetof(struct sockaddr_un, sun_path) + \ - (_sa->sun_path[0] == 0 ? \ - 1 + strnlen(_sa->sun_path+1, sizeof(_sa->sun_path)-1) : \ - strnlen(_sa->sun_path, sizeof(_sa->sun_path))+1); \ - }) +size_t sockaddr_un_len(const struct sockaddr_un *sa); -#define SOCKADDR_LEN(saddr) \ - ({ \ - const union sockaddr_union *__sa = &(saddr); \ - size_t _len; \ - switch (__sa->sa.sa_family) { \ - case AF_INET: \ - _len = sizeof(struct sockaddr_in); \ - break; \ - case AF_INET6: \ - _len = sizeof(struct sockaddr_in6); \ - break; \ - case AF_UNIX: \ - _len = SOCKADDR_UN_LEN(__sa->un); \ - break; \ - case AF_PACKET: \ - _len = SOCKADDR_LL_LEN(__sa->ll); \ - break; \ - case AF_NETLINK: \ - _len = sizeof(struct sockaddr_nl); \ - break; \ - case AF_VSOCK: \ - _len = sizeof(struct sockaddr_vm); \ - break; \ - default: \ - assert_not_reached(); \ - } \ - _len; \ - }) +size_t sockaddr_len(const union sockaddr_union *sa); int socket_ioctl_fd(void); @@ -322,6 +271,8 @@ static inline int getsockopt_int(int fd, int level, int optname, int *ret) { int socket_bind_to_ifname(int fd, const char *ifname); int socket_bind_to_ifindex(int fd, int ifindex); +int socket_autobind(int fd, char **ret_name); + /* Define a 64-bit version of timeval/timespec in any case, even on 32-bit userspace. */ struct timeval_large { uint64_t tvl_sec, tvl_usec; diff --git a/src/libnm-systemd-shared/src/basic/stat-util.c b/src/libnm-systemd-shared/src/basic/stat-util.c index 2181ee2df5..f3a1db5ed7 100644 --- a/src/libnm-systemd-shared/src/basic/stat-util.c +++ b/src/libnm-systemd-shared/src/basic/stat-util.c @@ -16,10 +16,10 @@ #include "filesystems.h" #include "fs-util.h" #include "hash-funcs.h" +#include "log.h" #include "macro.h" #include "missing_fs.h" #include "missing_magic.h" -#include "missing_syscall.h" #include "mountpoint-util.h" #include "nulstr-util.h" #include "parse-util.h" @@ -172,7 +172,7 @@ int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) struct dirent *de; ssize_t n; - n = getdents64(fd, buf, m); + n = posix_getdents(fd, buf, m, /* flags = */ 0); if (n < 0) return -errno; if (n == 0) @@ -298,7 +298,7 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl flags |= AT_EMPTY_PATH; } - int ntha_flags = (flags & AT_EMPTY_PATH) | (FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW) ? 0 : AT_SYMLINK_FOLLOW); + int ntha_flags = at_flags_normalize_follow(flags) & (AT_EMPTY_PATH|AT_SYMLINK_FOLLOW); _cleanup_free_ struct file_handle *ha = NULL, *hb = NULL; int mntida = -1, mntidb = -1; @@ -470,8 +470,8 @@ bool statx_inode_same(const struct statx *a, const struct statx *b) { a->stx_ino == b->stx_ino; } -bool statx_mount_same(const struct new_statx *a, const struct new_statx *b) { - if (!new_statx_is_set(a) || !new_statx_is_set(b)) +bool statx_mount_same(const struct statx *a, const struct statx *b) { + if (!statx_is_set(a) || !statx_is_set(b)) return false; /* if we have the mount ID, that's all we need */ @@ -483,87 +483,20 @@ bool statx_mount_same(const struct new_statx *a, const struct new_statx *b) { a->stx_dev_minor == b->stx_dev_minor; } -static bool is_statx_fatal_error(int err, int flags) { - assert(err < 0); - - /* If statx() is not supported or if we see EPERM (which might indicate seccomp filtering or so), - * let's do a fallback. Note that on EACCES we'll not fall back, since that is likely an indication of - * fs access issues, which we should propagate. */ - if (ERRNO_IS_NOT_SUPPORTED(err) || err == -EPERM) - return false; - - /* When unsupported flags are specified, glibc's fallback function returns -EINVAL. - * See statx_generic() in glibc. */ - if (err != -EINVAL) - return true; - - if ((flags & ~(AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | AT_STATX_SYNC_AS_STAT)) != 0) - return false; /* Unsupported flags are specified. Let's try to use our implementation. */ - - return true; -} - -int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct statx *sx) { - static bool avoid_statx = false; - struct stat st; - int r; - - if (!avoid_statx) { - r = RET_NERRNO(statx(dfd, path, flags, mask, sx)); - if (r >= 0 || is_statx_fatal_error(r, flags)) - return r; - - avoid_statx = true; - } - - /* Only do fallback if fstatat() supports the flag too, or if it's one of the sync flags, which are - * OK to ignore */ - if ((flags & ~(AT_EMPTY_PATH|AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW| - AT_STATX_SYNC_AS_STAT|AT_STATX_FORCE_SYNC|AT_STATX_DONT_SYNC)) != 0) - return -EOPNOTSUPP; - - if (fstatat(dfd, path, &st, flags & (AT_EMPTY_PATH|AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW)) < 0) - return -errno; - - *sx = (struct statx) { - .stx_mask = STATX_TYPE|STATX_MODE| - STATX_NLINK|STATX_UID|STATX_GID| - STATX_ATIME|STATX_MTIME|STATX_CTIME| - STATX_INO|STATX_SIZE|STATX_BLOCKS, - .stx_blksize = st.st_blksize, - .stx_nlink = st.st_nlink, - .stx_uid = st.st_uid, - .stx_gid = st.st_gid, - .stx_mode = st.st_mode, - .stx_ino = st.st_ino, - .stx_size = st.st_size, - .stx_blocks = st.st_blocks, - .stx_rdev_major = major(st.st_rdev), - .stx_rdev_minor = minor(st.st_rdev), - .stx_dev_major = major(st.st_dev), - .stx_dev_minor = minor(st.st_dev), - .stx_atime.tv_sec = st.st_atim.tv_sec, - .stx_atime.tv_nsec = st.st_atim.tv_nsec, - .stx_mtime.tv_sec = st.st_mtim.tv_sec, - .stx_mtime.tv_nsec = st.st_mtim.tv_nsec, - .stx_ctime.tv_sec = st.st_ctim.tv_sec, - .stx_ctime.tv_nsec = st.st_ctim.tv_nsec, - }; - - return 0; -} - int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { _cleanup_close_ int fd = -EBADF; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(ret); - fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return fd; + if (!isempty(path)) { + fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return fd; + dir_fd = fd; + } - return RET_NERRNO(fstatfs(fd, ret)); + return RET_NERRNO(fstatfs(dir_fd, ret)); } void inode_hash_func(const struct stat *q, struct siphash *state) { diff --git a/src/libnm-systemd-shared/src/basic/stat-util.h b/src/libnm-systemd-shared/src/basic/stat-util.h index 7556f8f59a..e4fd422139 100644 --- a/src/libnm-systemd-shared/src/basic/stat-util.h +++ b/src/libnm-systemd-shared/src/basic/stat-util.h @@ -11,7 +11,6 @@ #include "fs-util.h" #include "macro.h" -#include "missing_stat.h" #include "siphash24.h" #include "time-util.h" @@ -90,31 +89,10 @@ bool stat_inode_same(const struct stat *a, const struct stat *b); bool stat_inode_unmodified(const struct stat *a, const struct stat *b); 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); - -int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct statx *sx); +bool statx_mount_same(const struct statx *a, const struct statx *b); int xstatfsat(int dir_fd, const char *path, struct statfs *ret); -#if HAS_FEATURE_MEMORY_SANITIZER -# warning "Explicitly initializing struct statx, to work around msan limitation. Please remove as soon as msan has been updated to not require this." -# define STRUCT_STATX_DEFINE(var) \ - struct statx var = {} -# define STRUCT_NEW_STATX_DEFINE(var) \ - union { \ - struct statx sx; \ - struct new_statx nsx; \ - } var = {} -#else -# define STRUCT_STATX_DEFINE(var) \ - struct statx var -# define STRUCT_NEW_STATX_DEFINE(var) \ - union { \ - struct statx sx; \ - struct new_statx nsx; \ - } var -#endif - static inline usec_t statx_timestamp_load(const struct statx_timestamp *ts) { return timespec_load(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec }); } @@ -140,6 +118,3 @@ static inline bool stat_is_set(const struct stat *st) { 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 0a2239d022..bd5871821c 100644 --- a/src/libnm-systemd-shared/src/basic/stdio-util.h +++ b/src/libnm-systemd-shared/src/basic/stdio-util.h @@ -6,6 +6,7 @@ #include #include +#include "assert-util.h" #include "macro.h" _printf_(3, 4) diff --git a/src/libnm-systemd-shared/src/basic/string-util.c b/src/libnm-systemd-shared/src/basic/string-util.c index b2919164d6..a836363e48 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.c +++ b/src/libnm-systemd-shared/src/basic/string-util.c @@ -14,6 +14,7 @@ #include "glyph-util.h" #include "gunicode.h" #include "locale-util.h" +#include "log.h" #include "macro.h" #include "memory-util.h" #include "memstream-util.h" @@ -46,35 +47,42 @@ char* first_word(const char *s, const char *word) { return (char*) nw; } -char* strnappend(const char *s, const char *suffix, size_t b) { - size_t a; - char *r; +char* strprepend(char **x, const char *s) { + assert(x); - if (!s && !suffix) - return strdup(""); + if (isempty(s) && *x) + return *x; - if (!s) - return strndup(suffix, b); - - if (!suffix) - return strdup(s); - - assert(s); - assert(suffix); - - a = strlen(s); - if (b > SIZE_MAX - a) + char *p = strjoin(strempty(s), *x); + if (!p) return NULL; - r = new(char, a+b+1); - if (!r) - return NULL; + free_and_replace(*x, p); + return *x; +} - memcpy(r, s, a); - memcpy(r+a, suffix, b); - r[a+b] = 0; +char* strextendn(char **x, const char *s, size_t l) { + assert(x); + assert(s || l == 0); - return r; + if (l > 0) + l = strnlen(s, l); /* ignore trailing noise */ + + if (l > 0 || !*x) { + size_t q; + char *m; + + q = strlen_ptr(*x); + m = realloc(*x, q + l + 1); + if (!m) + return NULL; + + *mempcpy_typesafe(m + q, s, l) = 0; + + *x = m; + } + + return *x; } char* strstrip(char *s) { @@ -240,7 +248,7 @@ bool string_has_cc(const char *p, const char *ok) { } static int write_ellipsis(char *buf, bool unicode) { - const char *s = special_glyph_full(SPECIAL_GLYPH_ELLIPSIS, unicode); + const char *s = glyph_full(GLYPH_ELLIPSIS, unicode); assert(strlen(s) == 3); memcpy(buf, s, 3); return 3; @@ -328,10 +336,6 @@ static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_le case 2: if (!is_locale_utf8()) return strdup(".."); - - break; - - default: break; } @@ -975,33 +979,6 @@ oom: return -ENOMEM; } -char* strextendn(char **x, const char *s, size_t l) { - assert(x); - assert(s || l == 0); - - if (l == SIZE_MAX) - l = strlen_ptr(s); - else if (l > 0) - l = strnlen(s, l); /* ignore trailing noise */ - - if (l > 0 || !*x) { - size_t q; - char *m; - - q = strlen_ptr(*x); - m = realloc(*x, q + l + 1); - if (!m) - return NULL; - - memcpy_safe(m + q, s, l); - m[q + l] = 0; - - *x = m; - } - - return *x; -} - char* strrep(const char *s, unsigned n) { char *r, *p; size_t l; @@ -1066,6 +1043,15 @@ int free_and_strdup(char **p, const char *s) { return 1; } +int free_and_strdup_warn(char **p, const char *s) { + int r; + + r = free_and_strdup(p, s); + if (r < 0) + return log_oom(); + return r; +} + int free_and_strndup(char **p, const char *s, size_t l) { char *t; @@ -1490,3 +1476,19 @@ char* strrstr(const char *haystack, const char *needle) { } return NULL; } + +size_t str_common_prefix(const char *a, const char *b) { + assert(a); + assert(b); + + /* Returns the length of the common prefix of the two specified strings, or SIZE_MAX in case the + * strings are fully identical. */ + + for (size_t n = 0;; n++) { + char c = a[n]; + if (c != b[n]) + return n; + if (c == 0) + return SIZE_MAX; + } +} diff --git a/src/libnm-systemd-shared/src/basic/string-util.h b/src/libnm-systemd-shared/src/basic/string-util.h index 1bcb1c40e3..91e63c9b12 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.h +++ b/src/libnm-systemd-shared/src/basic/string-util.h @@ -106,7 +106,8 @@ static inline const char* empty_or_dash_to_null(const char *p) { char* first_word(const char *s, const char *word) _pure_; -char* strnappend(const char *s, const char *suffix, size_t length); +char* strprepend(char **x, const char *s); +char* strextendn(char **x, const char *s, size_t l) _nonnull_if_nonzero_(2, 3); #define strjoin(a, ...) strextend_with_separator_internal(NULL, NULL, a, __VA_ARGS__, NULL) @@ -193,8 +194,6 @@ char* strextend_with_separator_internal(char **x, const char *separator, ...) _s #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); - 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__) @@ -216,15 +215,8 @@ char* strrep(const char *s, unsigned n); 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) { - int r; - - r = free_and_strdup(p, s); - if (r < 0) - return log_oom(); - return r; -} -int free_and_strndup(char **p, const char *s, size_t l); +int free_and_strdup_warn(char **p, const char *s); +int free_and_strndup(char **p, const char *s, size_t l) _nonnull_if_nonzero_(2, 3); int strdup_to_full(char **ret, const char *src); static inline int strdup_to(char **ret, const char *src) { @@ -309,3 +301,5 @@ bool version_is_valid_versionspec(const char *s); ssize_t strlevenshtein(const char *x, const char *y); char* strrstr(const char *haystack, const char *needle); + +size_t str_common_prefix(const char *a, const char *b); diff --git a/src/libnm-systemd-shared/src/basic/strv.c b/src/libnm-systemd-shared/src/basic/strv.c index c9c4551cdc..2f30854146 100644 --- a/src/libnm-systemd-shared/src/basic/strv.c +++ b/src/libnm-systemd-shared/src/basic/strv.c @@ -12,6 +12,7 @@ #include "extract-word.h" #include "fileio.h" #include "gunicode.h" +#include "log.h" #include "memory-util.h" #include "nulstr-util.h" #include "sort-util.h" @@ -631,7 +632,7 @@ int strv_insert(char ***l, size_t position, char *value) { n = strv_length(*l); position = MIN(position, n); - /* check for overflow and increase*/ + /* check for overflow and increase */ if (n > SIZE_MAX - 2) return -ENOMEM; m = n + 2; @@ -861,11 +862,11 @@ int strv_compare(char * const *a, char * const *b) { return 0; } -bool strv_equal_ignore_order(char **a, char **b) { +bool strv_equal_ignore_order(char * const *a, char * const *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) */ + * consists of repetitions). */ if (a == b) return true; @@ -976,14 +977,16 @@ bool strv_fnmatch_full( } char** strv_skip(char **l, size_t n) { - while (n > 0) { if (strv_isempty(l)) - return l; + return NULL; l++, n--; } + /* To simplify callers, always return NULL instead of a zero-item array. */ + if (strv_isempty(l)) + return NULL; return l; } @@ -1063,7 +1066,22 @@ 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); +void string_strv_hashmap_remove(Hashmap *h, const char *key, const char *value) { + assert(key); + + if (value) { + char **l = hashmap_get(h, key); + if (!l) + return; + + strv_remove(l, value); + if (!strv_isempty(l)) + return; + } + + _unused_ _cleanup_free_ char *key_free = NULL; + strv_free(hashmap_remove2(h, key, (void**) &key_free)); +} static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const char *value) { char **l; @@ -1115,7 +1133,7 @@ int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value HA assert(key); assert(value); - r = _hashmap_ensure_allocated(h, &string_strv_hash_ops HASHMAP_DEBUG_PASS_ARGS); + r = _hashmap_ensure_allocated(h, &string_hash_ops_free_strv_free HASHMAP_DEBUG_PASS_ARGS); if (r < 0) return r; @@ -1129,7 +1147,7 @@ int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const assert(key); assert(value); - r = _ordered_hashmap_ensure_allocated(h, &string_strv_hash_ops HASHMAP_DEBUG_PASS_ARGS); + r = _ordered_hashmap_ensure_allocated(h, &string_hash_ops_free_strv_free HASHMAP_DEBUG_PASS_ARGS); if (r < 0) return r; @@ -1223,3 +1241,24 @@ int strv_rebreak_lines(char **l, size_t width, char ***ret) { *ret = TAKE_PTR(broken); return 0; } + +char** strv_filter_prefix(char * const *l, const char *prefix) { + + /* Allocates a copy of 'l', but only copies over entries starting with 'prefix' */ + + if (isempty(prefix)) + return strv_copy(l); + + _cleanup_strv_free_ char **f = NULL; + size_t sz = 0; + + STRV_FOREACH(i, l) { + if (!startswith(*i, prefix)) + continue; + + if (strv_extend_with_size(&f, &sz, *i) < 0) + return NULL; + } + + return TAKE_PTR(f); +} diff --git a/src/libnm-systemd-shared/src/basic/strv.h b/src/libnm-systemd-shared/src/basic/strv.h index 86ba06f835..0ca90087b4 100644 --- a/src/libnm-systemd-shared/src/basic/strv.h +++ b/src/libnm-systemd-shared/src/basic/strv.h @@ -33,7 +33,7 @@ char** strv_free_erase(char **l); DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase); #define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep) -void strv_free_many(char ***strvs, size_t n); +void strv_free_many(char ***strvs, size_t n) _nonnull_if_nonzero_(1, 2); char** strv_copy_n(char * const *l, size_t n); static inline char** strv_copy(char * const *l) { @@ -96,7 +96,7 @@ 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); +bool strv_equal_ignore_order(char * const *a, char * const *b); char** strv_new_internal(const char *x, ...) _sentinel_; char** strv_new_ap(const char *x, va_list ap); @@ -258,9 +258,15 @@ 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) +void string_strv_hashmap_remove(Hashmap *h, const char *key, const char *value); +static inline void string_strv_ordered_hashmap_remove(OrderedHashmap *h, const char *key, const char *value) { + string_strv_hashmap_remove(PLAIN_HASHMAP(h), key, value); +} 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); + +char** strv_filter_prefix(char * const *l, const char *prefix); diff --git a/src/libnm-systemd-shared/src/basic/time-util.c b/src/libnm-systemd-shared/src/basic/time-util.c index aa51334100..fcbedf9e9d 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.c +++ b/src/libnm-systemd-shared/src/basic/time-util.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "alloc-util.h" @@ -17,8 +18,6 @@ #include "io-util.h" #include "log.h" #include "macro.h" -#include "missing_threads.h" -#include "missing_timerfd.h" #include "parse-util.h" #include "path-util.h" #include "process-util.h" @@ -1636,7 +1635,7 @@ int mktime_or_timegm_usec( 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!)*/ + 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; diff --git a/src/libnm-systemd-shared/src/basic/time-util.h b/src/libnm-systemd-shared/src/basic/time-util.h index 14d660ee7e..3f6e3c9f41 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.h +++ b/src/libnm-systemd-shared/src/basic/time-util.h @@ -222,8 +222,8 @@ static inline int usleep_safe(usec_t usec) { if (usec == 0) return 0; - // FIXME: use RET_NERRNO() macro here. Currently, this header cannot include errno-util.h. - return clock_nanosleep(CLOCK_MONOTONIC, 0, TIMESPEC_STORE(usec), NULL) < 0 ? -errno : 0; + /* `clock_nanosleep()` does not use `errno`, but returns positive error codes. */ + return -clock_nanosleep(CLOCK_MONOTONIC, 0, TIMESPEC_STORE(usec), NULL); } /* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.c b/src/libnm-systemd-shared/src/basic/tmpfile-util.c index 5addd8e8a9..371f2ae3a8 100644 --- a/src/libnm-systemd-shared/src/basic/tmpfile-util.c +++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.c @@ -7,6 +7,7 @@ #include "fileio.h" #include "fs-util.h" #include "hexdecoct.h" +#include "log.h" #include "macro.h" #include "memfd-util.h" #include "missing_fcntl.h" diff --git a/src/libnm-systemd-shared/src/basic/user-util.c b/src/libnm-systemd-shared/src/basic/user-util.c new file mode 100644 index 0000000000..96b8fb270c --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/user-util.c @@ -0,0 +1,1239 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sd-messages.h" + +#include "alloc-util.h" +#include "chase.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-util.h" +#include "lock-util.h" +#include "log.h" +#include "macro.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "random-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" +#include "utf8.h" + +bool uid_is_valid(uid_t uid) { + + /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.436. */ + + /* Some libc APIs use UID_INVALID as special placeholder */ + if (uid == (uid_t) UINT32_C(0xFFFFFFFF)) + return false; + + /* A long time ago UIDs where 16 bit, hence explicitly avoid the 16-bit -1 too */ + if (uid == (uid_t) UINT32_C(0xFFFF)) + return false; + + return true; +} + +int parse_uid(const char *s, uid_t *ret) { + uint32_t uid = 0; + int r; + + assert(s); + + assert_cc(sizeof(uid_t) == sizeof(uint32_t)); + + /* We are very strict when parsing UIDs, and prohibit +/- as prefix, leading zero as prefix, and + * whitespace. We do this, since this call is often used in a context where we parse things as UID + * first, and if that doesn't work we fall back to NSS. Thus we really want to make sure that UIDs + * are parsed as UIDs only if they really really look like UIDs. */ + r = safe_atou32_full(s, 10 + | SAFE_ATO_REFUSE_PLUS_MINUS + | SAFE_ATO_REFUSE_LEADING_ZERO + | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &uid); + if (r < 0) + return r; + + if (!uid_is_valid(uid)) + return -ENXIO; /* we return ENXIO instead of EINVAL + * here, to make it easy to distinguish + * invalid numeric uids from invalid + * strings. */ + + if (ret) + *ret = uid; + + return 0; +} + +int parse_uid_range(const char *s, uid_t *ret_lower, uid_t *ret_upper) { + _cleanup_free_ char *word = NULL; + uid_t l, u; + int r; + + assert(s); + assert(ret_lower); + assert(ret_upper); + + r = extract_first_word(&s, &word, "-", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + r = parse_uid(word, &l); + if (r < 0) + return r; + + /* Check for the upper bound and extract it if needed */ + if (!s) + /* Single number with no dash. */ + u = l; + else if (!*s) + /* Trailing dash is an error. */ + return -EINVAL; + else { + r = parse_uid(s, &u); + if (r < 0) + return r; + + if (l > u) + return -EINVAL; + } + + *ret_lower = l; + *ret_upper = u; + return 0; +} + +char* getlogname_malloc(void) { + uid_t uid; + struct stat st; + + if (isatty_safe(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) + uid = st.st_uid; + else + uid = getuid(); + + return uid_to_name(uid); +} + +char* getusername_malloc(void) { + const char *e; + + e = secure_getenv("USER"); + if (e) + return strdup(e); + + return uid_to_name(getuid()); +} + +bool is_nologin_shell(const char *shell) { + return PATH_IN_SET(shell, + /* 'nologin' is the friendliest way to disable logins for a user account. It prints a nice + * message and exits. Different distributions place the binary at different places though, + * hence let's list them all. */ + "/bin/nologin", + "/sbin/nologin", + "/usr/bin/nologin", + "/usr/sbin/nologin", + /* 'true' and 'false' work too for the same purpose, but are less friendly as they don't do + * any message printing. Different distributions place the binary at various places but at + * least not in the 'sbin' directory. */ + "/bin/false", + "/usr/bin/false", + "/bin/true", + "/usr/bin/true"); +} + +const char* default_root_shell_at(int rfd) { + /* We want to use the preferred shell, i.e. DEFAULT_USER_SHELL, which usually + * will be /bin/bash. Fall back to /bin/sh if DEFAULT_USER_SHELL is not found, + * or any access errors. */ + + assert(rfd >= 0 || rfd == AT_FDCWD); + + int r = chaseat(rfd, DEFAULT_USER_SHELL, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to look up shell '%s': %m", DEFAULT_USER_SHELL); + if (r > 0) + return DEFAULT_USER_SHELL; + + return "/bin/sh"; +} + +const char* default_root_shell(const char *root) { + _cleanup_close_ int rfd = -EBADF; + + rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH); + if (rfd < 0) + return "/bin/sh"; + + return default_root_shell_at(rfd); +} + +static int synthesize_user_creds( + const char **username, + uid_t *ret_uid, gid_t *ret_gid, + const char **ret_home, + const char **ret_shell, + UserCredsFlags flags) { + + assert(username); + assert(*username); + + /* We enforce some special rules for uid=0 and uid=65534: in order to avoid NSS lookups for root we hardcode + * their user record data. */ + + if (STR_IN_SET(*username, "root", "0")) { + *username = "root"; + + if (ret_uid) + *ret_uid = 0; + if (ret_gid) + *ret_gid = 0; + if (ret_home) + *ret_home = "/root"; + if (ret_shell) + *ret_shell = default_root_shell(NULL); + + return 0; + } + + if (STR_IN_SET(*username, NOBODY_USER_NAME, "65534") && + synthesize_nobody()) { + *username = NOBODY_USER_NAME; + + if (ret_uid) + *ret_uid = UID_NOBODY; + if (ret_gid) + *ret_gid = GID_NOBODY; + if (ret_home) + *ret_home = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : "/"; + if (ret_shell) + *ret_shell = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : NOLOGIN; + + return 0; + } + + return -ENOMEDIUM; +} + +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) { + + bool patch_username = false; + uid_t u = UID_INVALID; + struct passwd *p; + int r; + + assert(username); + assert(*username); + assert((ret_home || ret_shell) || !(flags & (USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_CLEAN))); + + if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) || + (!ret_home && !ret_shell)) { + + /* So here's the deal: normally, we'll try to synthesize all records we can synthesize, and override + * the user database with that. However, if the user specifies USER_CREDS_PREFER_NSS then the + * user database will override the synthetic records instead — except if the user is only interested in + * the UID and/or GID (but not the home directory, or the shell), in which case we'll always override + * the user database (i.e. the USER_CREDS_PREFER_NSS flag has no effect in this case). Why? + * Simply because there are valid usecase where the user might change the home directory or the shell + * of the relevant users, but changing the UID/GID mappings for them is something we explicitly don't + * support. */ + + r = synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags); + if (r >= 0) + return 0; + if (r != -ENOMEDIUM) /* not a username we can synthesize */ + return r; + } + + if (parse_uid(*username, &u) >= 0) { + errno = 0; + p = getpwuid(u); + + /* If there are multiple users with the same id, make sure to leave $USER to the configured value + * instead of the first occurrence in the database. However if the uid was configured by a numeric uid, + * then let's pick the real username from /etc/passwd. */ + if (p) + patch_username = true; + else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !ret_gid && !ret_home && !ret_shell) { + + /* If the specified user is a numeric UID and it isn't in the user database, and the caller + * passed USER_CREDS_ALLOW_MISSING and was only interested in the UID, then just return that + * and don't complain. */ + + if (ret_uid) + *ret_uid = u; + + return 0; + } + } else { + errno = 0; + p = getpwnam(*username); + } + if (!p) { + /* getpwnam() may fail with ENOENT if /etc/passwd is missing. + * For us that is equivalent to the name not being defined. */ + r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno; + + /* If the user requested that we only synthesize as fallback, do so now */ + if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) + if (synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags) >= 0) + return 0; + + return r; + } + + if (ret_uid && !uid_is_valid(p->pw_uid)) + return -EBADMSG; + + if (ret_gid && !gid_is_valid(p->pw_gid)) + return -EBADMSG; + + if (ret_uid) + *ret_uid = p->pw_uid; + + if (ret_gid) + *ret_gid = p->pw_gid; + + if (ret_home) + /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */ + *ret_home = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && empty_or_root(p->pw_dir)) || + (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_dir) || !path_is_absolute(p->pw_dir))) + ? NULL : p->pw_dir; + + if (ret_shell) + *ret_shell = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && shell_is_placeholder(p->pw_shell)) || + (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_shell) || !path_is_absolute(p->pw_shell))) + ? NULL : p->pw_shell; + + if (patch_username) + *username = p->pw_name; + + return 0; +} + +static int synthesize_group_creds( + const char **groupname, + gid_t *ret_gid) { + + assert(groupname); + assert(*groupname); + + if (STR_IN_SET(*groupname, "root", "0")) { + *groupname = "root"; + + if (ret_gid) + *ret_gid = 0; + + return 0; + } + + if (STR_IN_SET(*groupname, NOBODY_GROUP_NAME, "65534") && + synthesize_nobody()) { + *groupname = NOBODY_GROUP_NAME; + + if (ret_gid) + *ret_gid = GID_NOBODY; + + return 0; + } + + return -ENOMEDIUM; +} + +int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags) { + bool patch_groupname = false; + struct group *g; + gid_t id; + int r; + + assert(groupname); + assert(*groupname); + + if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) { + r = synthesize_group_creds(groupname, ret_gid); + if (r >= 0) + return 0; + if (r != -ENOMEDIUM) /* not a groupname we can synthesize */ + return r; + } + + if (parse_gid(*groupname, &id) >= 0) { + errno = 0; + g = getgrgid(id); + + if (g) + patch_groupname = true; + else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING)) { + if (ret_gid) + *ret_gid = id; + + return 0; + } + } else { + errno = 0; + g = getgrnam(*groupname); + } + + if (!g) { + /* getgrnam() may fail with ENOENT if /etc/group is missing. + * For us that is equivalent to the name not being defined. */ + r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno; + + if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) + if (synthesize_group_creds(groupname, ret_gid) >= 0) + return 0; + + return r; + } + + if (ret_gid) { + if (!gid_is_valid(g->gr_gid)) + return -EBADMSG; + + *ret_gid = g->gr_gid; + } + + if (patch_groupname) + *groupname = g->gr_name; + + return 0; +} + +char* uid_to_name(uid_t uid) { + char *ret; + int r; + + /* Shortcut things to avoid NSS lookups */ + if (uid == 0) + return strdup("root"); + if (uid == UID_NOBODY && synthesize_nobody()) + return strdup(NOBODY_USER_NAME); + + if (uid_is_valid(uid)) { + _cleanup_free_ struct passwd *pw = NULL; + + r = getpwuid_malloc(uid, &pw); + if (r >= 0) + return strdup(pw->pw_name); + } + + if (asprintf(&ret, UID_FMT, uid) < 0) + return NULL; + + return ret; +} + +char* gid_to_name(gid_t gid) { + char *ret; + int r; + + if (gid == 0) + return strdup("root"); + if (gid == GID_NOBODY && synthesize_nobody()) + return strdup(NOBODY_GROUP_NAME); + + if (gid_is_valid(gid)) { + _cleanup_free_ struct group *gr = NULL; + + r = getgrgid_malloc(gid, &gr); + if (r >= 0) + return strdup(gr->gr_name); + } + + if (asprintf(&ret, GID_FMT, gid) < 0) + return NULL; + + return ret; +} + +static bool gid_list_has(const gid_t *list, size_t size, gid_t val) { + assert(list || size == 0); + + FOREACH_ARRAY(i, list, size) + if (*i == val) + return true; + + return false; +} + +int in_gid(gid_t gid) { + _cleanup_free_ gid_t *gids = NULL; + int ngroups; + + if (getgid() == gid) + return 1; + + if (getegid() == gid) + return 1; + + if (!gid_is_valid(gid)) + return -EINVAL; + + ngroups = getgroups_alloc(&gids); + if (ngroups < 0) + return ngroups; + + return gid_list_has(gids, ngroups, gid); +} + +int in_group(const char *name) { + int r; + gid_t gid; + + r = get_group_creds(&name, &gid, 0); + if (r < 0) + return r; + + return in_gid(gid); +} + +int merge_gid_lists(const gid_t *list1, size_t size1, const gid_t *list2, size_t size2, gid_t **ret) { + size_t nresult = 0; + assert(ret); + + if (size2 > INT_MAX - size1) + return -ENOBUFS; + + gid_t *buf = new(gid_t, size1 + size2); + if (!buf) + return -ENOMEM; + + /* Duplicates need to be skipped on merging, otherwise they'll be passed on and stored in the kernel. */ + for (size_t i = 0; i < size1; i++) + if (!gid_list_has(buf, nresult, list1[i])) + buf[nresult++] = list1[i]; + for (size_t i = 0; i < size2; i++) + if (!gid_list_has(buf, nresult, list2[i])) + buf[nresult++] = list2[i]; + *ret = buf; + return (int)nresult; +} + +int getgroups_alloc(gid_t **ret) { + int ngroups = 8; + + assert(ret); + + for (unsigned attempt = 0;;) { + _cleanup_free_ gid_t *p = NULL; + + p = new(gid_t, ngroups); + if (!p) + return -ENOMEM; + + ngroups = getgroups(ngroups, p); + if (ngroups > 0) { + *ret = TAKE_PTR(p); + return ngroups; + } + if (ngroups == 0) + break; + if (errno != EINVAL) + return -errno; + + /* Give up eventually */ + if (attempt++ > 10) + return -EINVAL; + + /* Get actual size needed, and size the array explicitly. Note that this is potentially racy + * to use (in multi-threaded programs), hence let's call this in a loop. */ + ngroups = getgroups(0, NULL); + if (ngroups < 0) + return -errno; + if (ngroups == 0) + break; + } + + *ret = NULL; + return 0; +} + +int get_home_dir(char **ret) { + _cleanup_free_ struct passwd *p = NULL; + const char *e; + uid_t u; + int r; + + assert(ret); + + /* Take the user specified one */ + e = secure_getenv("HOME"); + if (e && path_is_valid(e) && path_is_absolute(e)) + goto found; + + /* Hardcode home directory for root and nobody to avoid NSS */ + u = getuid(); + if (u == 0) { + e = "/root"; + goto found; + } + if (u == UID_NOBODY && synthesize_nobody()) { + e = "/"; + goto found; + } + + /* Check the database... */ + r = getpwuid_malloc(u, &p); + if (r < 0) + return r; + + e = p->pw_dir; + if (!path_is_valid(e) || !path_is_absolute(e)) + return -EINVAL; + + found: + return path_simplify_alloc(e, ret); +} + +int get_shell(char **ret) { + _cleanup_free_ struct passwd *p = NULL; + const char *e; + uid_t u; + int r; + + assert(ret); + + /* Take the user specified one */ + e = secure_getenv("SHELL"); + if (e && path_is_valid(e) && path_is_absolute(e)) + goto found; + + /* Hardcode shell for root and nobody to avoid NSS */ + u = getuid(); + if (u == 0) { + e = default_root_shell(NULL); + goto found; + } + if (u == UID_NOBODY && synthesize_nobody()) { + e = NOLOGIN; + goto found; + } + + /* Check the database... */ + r = getpwuid_malloc(u, &p); + if (r < 0) + return r; + + e = p->pw_shell; + if (!path_is_valid(e) || !path_is_absolute(e)) + return -EINVAL; + + found: + return path_simplify_alloc(e, ret); +} + +int fully_set_uid_gid(uid_t uid, gid_t gid, const gid_t supplementary_gids[], size_t n_supplementary_gids) { + int r; + + assert(supplementary_gids || n_supplementary_gids == 0); + + /* Sets all UIDs and all GIDs to the specified ones. Drops all auxiliary GIDs */ + + r = maybe_setgroups(n_supplementary_gids, supplementary_gids); + if (r < 0) + return r; + + if (gid_is_valid(gid)) + if (setresgid(gid, gid, gid) < 0) + return -errno; + + if (uid_is_valid(uid)) + if (setresuid(uid, uid, uid) < 0) + return -errno; + + return 0; +} + +int take_etc_passwd_lock(const char *root) { + int r; + + /* This is roughly the same as lckpwdf(), but not as awful. We don't want to use alarm() and signals, + * hence we implement our own trivial version of this. + * + * Note that shadow-utils also takes per-database locks in addition to lckpwdf(). However, we don't, + * given that they are redundant: they invoke lckpwdf() first and keep it during everything they do. + * The per-database locks are awfully racy, and thus we just won't do them. */ + + _cleanup_free_ char *path = path_join(root, ETC_PASSWD_LOCK_PATH); + if (!path) + return log_oom_debug(); + + (void) mkdir_parents(path, 0755); + + _cleanup_close_ int fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600); + if (fd < 0) + return log_debug_errno(errno, "Cannot open %s: %m", path); + + r = unposix_lock(fd, LOCK_EX); + if (r < 0) + return log_debug_errno(r, "Locking %s failed: %m", path); + + return TAKE_FD(fd); +} + +bool valid_user_group_name(const char *u, ValidUserFlags flags) { + const char *i; + + /* Checks if the specified name is a valid user/group name. There are two flavours of this call: + * strict mode is the default which is POSIX plus some extra rules; and relaxed mode where we accept + * pretty much everything except the really worst offending names. + * + * Whenever we synthesize users ourselves we should use the strict mode. But when we process users + * created by other stuff, let's be more liberal. */ + + if (isempty(u)) /* An empty user name is never valid */ + return false; + + if (parse_uid(u, NULL) >= 0) /* Something that parses as numeric UID string is valid exactly when the + * flag for it is set */ + return FLAGS_SET(flags, VALID_USER_ALLOW_NUMERIC); + + if (FLAGS_SET(flags, VALID_USER_RELAX)) { + + /* In relaxed mode we just check very superficially. Apparently SSSD and other stuff is + * extremely liberal (way too liberal if you ask me, even inserting "@" in user names, which + * is bound to cause problems for example when used with an MTA), hence only filter the most + * obvious cases, or where things would result in an invalid entry if such a user name would + * show up in /etc/passwd (or equivalent getent output). + * + * Note that we stepped far out of POSIX territory here. It's not our fault though, but + * SSSD's, Samba's and everybody else who ignored POSIX on this. (I mean, I am happy to step + * outside of POSIX' bounds any day, but I must say in this case I probably wouldn't + * have...) */ + + if (startswith(u, " ") || endswith(u, " ")) /* At least expect whitespace padding is removed + * at front and back (accept in the middle, since + * that's apparently a thing on Windows). Note + * that this also blocks usernames consisting of + * whitespace only. */ + return false; + + if (!utf8_is_valid(u)) /* We want to synthesize JSON from this, hence insist on UTF-8 */ + return false; + + if (string_has_cc(u, NULL)) /* CC characters are just dangerous (and \n in particular is the + * record separator in /etc/passwd), so we can't allow that. */ + return false; + + if (strpbrk(u, ":/")) /* Colons are the field separator in /etc/passwd, we can't allow + * that. Slashes are special to file systems paths and user names + * typically show up in the file system as home directories, hence + * don't allow slashes. */ + return false; + + if (in_charset(u, "0123456789")) /* Don't allow fully numeric strings, they might be confused + * with UIDs (note that this test is more broad than + * the parse_uid() test above, as it will cover more than + * the 32-bit range, and it will detect 65535 (which is in + * invalid UID, even though in the unsigned 32 bit range) */ + return false; + + if (u[0] == '-' && in_charset(u + 1, "0123456789")) /* Don't allow negative fully numeric + * strings either. After all some people + * write 65535 as -1 (even though that's + * not even true on 32-bit uid_t + * anyway) */ + return false; + + if (dot_or_dot_dot(u)) /* User names typically become home directory names, and these two are + * special in that context, don't allow that. */ + return false; + + /* Compare with strict result and warn if result doesn't match */ + if (FLAGS_SET(flags, VALID_USER_WARN) && !valid_user_group_name(u, 0)) + log_struct(LOG_NOTICE, + LOG_MESSAGE("Accepting user/group name '%s', which does not match strict user/group name rules.", u), + LOG_ITEM("USER_GROUP_NAME=%s", u), + LOG_MESSAGE_ID(SD_MESSAGE_UNSAFE_USER_NAME_STR)); + + /* Note that we make no restrictions on the length in relaxed mode! */ + } else { + long sz; + size_t l; + + /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.437. We are a bit stricter here + * however. Specifically we deviate from POSIX rules: + * + * - We don't allow empty user names (see above) + * - We require that names fit into the appropriate utmp field + * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator) + * - We don't allow dashes or digit as the first character + * + * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters. + */ + + if (!ascii_isalpha(u[0]) && + u[0] != '_') + return false; + + for (i = u+1; *i; i++) + if (!ascii_isalpha(*i) && + !ascii_isdigit(*i) && + !IN_SET(*i, '_', '-')) + return false; + + l = i - u; + + sz = sysconf(_SC_LOGIN_NAME_MAX); + assert_se(sz > 0); + + if (l > (size_t) sz) /* glibc: 256 */ + return false; + if (l > NAME_MAX) /* must fit in a filename: 255 */ + return false; + if (l > sizeof_field(struct utmpx, ut_user) - 1) /* must fit in utmp: 31 */ + return false; + } + + return true; +} + +bool valid_gecos(const char *d) { + + if (!d) + return false; + + if (!utf8_is_valid(d)) + return false; + + if (string_has_cc(d, NULL)) + return false; + + /* Colons are used as field separators, and hence not OK */ + if (strchr(d, ':')) + return false; + + return true; +} + +char* mangle_gecos(const char *d) { + char *mangled; + + /* Makes sure the provided string becomes valid as a GEGOS field, by dropping bad chars. glibc's + * putwent() only changes \n and : to spaces. We do more: replace all CC too, and remove invalid + * UTF-8 */ + + mangled = strdup(d); + if (!mangled) + return NULL; + + for (char *i = mangled; *i; i++) { + int len; + + if ((uint8_t) *i < (uint8_t) ' ' || *i == ':') { + *i = ' '; + continue; + } + + len = utf8_encoded_valid_unichar(i, SIZE_MAX); + if (len < 0) { + *i = ' '; + continue; + } + + i += len - 1; + } + + return mangled; +} + +bool valid_home(const char *p) { + /* Note that this function is also called by valid_shell(), any + * changes must account for that. */ + + if (isempty(p)) + return false; + + if (!utf8_is_valid(p)) + return false; + + if (string_has_cc(p, NULL)) + return false; + + if (!path_is_absolute(p)) + return false; + + if (!path_is_normalized(p)) + return false; + + /* Colons are used as field separators, and hence not OK */ + if (strchr(p, ':')) + return false; + + return true; +} + +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/. */ + if (!valid_home(p)) + return false; + + return !endswith(p, "/"); /* one additional restriction: shells may not be dirs */ +} + +int maybe_setgroups(size_t size, const gid_t *list) { + int r; + + /* Check if setgroups is allowed before we try to drop all the auxiliary groups */ + if (size == 0) { /* Dropping all aux groups? */ + _cleanup_free_ char *setgroups_content = NULL; + bool can_setgroups; + + r = read_one_line_file("/proc/self/setgroups", &setgroups_content); + if (r == -ENOENT) + /* Old kernels don't have /proc/self/setgroups, so assume we can use setgroups */ + can_setgroups = true; + else if (r < 0) + return r; + else + can_setgroups = streq(setgroups_content, "allow"); + + if (!can_setgroups) { + log_debug("Skipping setgroups(), /proc/self/setgroups is set to 'deny'"); + return 0; + } + } + + return RET_NERRNO(setgroups(size, list)); +} + +bool synthesize_nobody(void) { + /* Returns true when we shall synthesize the "nobody" user (which we do by default). This can be turned off by + * touching /etc/systemd/dont-synthesize-nobody in order to provide upgrade compatibility with legacy systems + * that used the "nobody" user name and group name for other UIDs/GIDs than 65534. + * + * Note that we do not employ any kind of synchronization on the following caching variable. If the variable is + * accessed in multi-threaded programs in the worst case it might happen that we initialize twice, but that + * shouldn't matter as each initialization should come to the same result. */ + static int cache = -1; + + if (cache < 0) + cache = access("/etc/systemd/dont-synthesize-nobody", F_OK) < 0; + + return cache; +} + +int putpwent_sane(const struct passwd *pw, FILE *stream) { + assert(pw); + assert(stream); + + errno = 0; + if (putpwent(pw, stream) != 0) + return errno_or_else(EIO); + + return 0; +} + +int putspent_sane(const struct spwd *sp, FILE *stream) { + assert(sp); + assert(stream); + + errno = 0; + if (putspent(sp, stream) != 0) + return errno_or_else(EIO); + + return 0; +} + +int putgrent_sane(const struct group *gr, FILE *stream) { + assert(gr); + assert(stream); + + errno = 0; + if (putgrent(gr, stream) != 0) + return errno_or_else(EIO); + + return 0; +} + +#if ENABLE_GSHADOW +int putsgent_sane(const struct sgrp *sg, FILE *stream) { + assert(sg); + assert(stream); + + errno = 0; + if (putsgent(sg, stream) != 0) + return errno_or_else(EIO); + + return 0; +} +#endif + +int fgetpwent_sane(FILE *stream, struct passwd **pw) { + assert(stream); + assert(pw); + + errno = 0; + struct passwd *p = fgetpwent(stream); + if (!p && !IN_SET(errno, 0, ENOENT)) + return -errno; + + *pw = p; + return !!p; +} + +int fgetspent_sane(FILE *stream, struct spwd **sp) { + assert(stream); + assert(sp); + + errno = 0; + struct spwd *s = fgetspent(stream); + if (!s && !IN_SET(errno, 0, ENOENT)) + return -errno; + + *sp = s; + return !!s; +} + +int fgetgrent_sane(FILE *stream, struct group **gr) { + assert(stream); + assert(gr); + + errno = 0; + struct group *g = fgetgrent(stream); + if (!g && !IN_SET(errno, 0, ENOENT)) + return -errno; + + *gr = g; + return !!g; +} + +#if ENABLE_GSHADOW +int fgetsgent_sane(FILE *stream, struct sgrp **sg) { + assert(stream); + assert(sg); + + errno = 0; + struct sgrp *s = fgetsgent(stream); + if (!s && !IN_SET(errno, 0, ENOENT)) + return -errno; + + *sg = s; + return !!s; +} +#endif + +int is_this_me(const char *username) { + uid_t uid; + int r; + + /* Checks if the specified username is our current one. Passed string might be a UID or a user name. */ + + r = get_user_creds(&username, &uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); + if (r < 0) + return r; + + return uid == getuid(); +} + +const char* get_home_root(void) { + const char *e; + + /* For debug purposes allow overriding where we look for home dirs */ + e = secure_getenv("SYSTEMD_HOME_ROOT"); + if (e && path_is_absolute(e) && path_is_normalized(e)) + return e; + + return "/home"; +} + +static size_t getpw_buffer_size(void) { + long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + return bufsize <= 0 ? 4096U : (size_t) bufsize; +} + +static bool errno_is_user_doesnt_exist(int error) { + /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are + * not found. */ + return IN_SET(abs(error), ENOENT, ESRCH, EBADF, EPERM); +} + +int getpwnam_malloc(const char *name, struct passwd **ret) { + size_t bufsize = getpw_buffer_size(); + int r; + + /* A wrapper around getpwnam_r() that allocates the necessary buffer on the heap. The caller must + * free() the returned structures! */ + + if (isempty(name)) + return -EINVAL; + + for (;;) { + _cleanup_free_ void *buf = NULL; + + buf = malloc(ALIGN(sizeof(struct passwd)) + bufsize); + if (!buf) + return -ENOMEM; + + struct passwd *pw = NULL; + r = getpwnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw); + if (r == 0) { + if (pw) { + if (ret) + *ret = TAKE_PTR(buf); + return 0; + } + + return -ESRCH; + } + + assert(r > 0); + + /* getpwnam() may fail with ENOENT if /etc/passwd is missing. For us that is equivalent to + * the name not being defined. */ + if (errno_is_user_doesnt_exist(r)) + return -ESRCH; + if (r != ERANGE) + return -r; + + if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd))) + return -ENOMEM; + bufsize *= 2; + } +} + +int getpwuid_malloc(uid_t uid, struct passwd **ret) { + size_t bufsize = getpw_buffer_size(); + int r; + + if (!uid_is_valid(uid)) + return -EINVAL; + + for (;;) { + _cleanup_free_ void *buf = NULL; + + buf = malloc(ALIGN(sizeof(struct passwd)) + bufsize); + if (!buf) + return -ENOMEM; + + struct passwd *pw = NULL; + r = getpwuid_r(uid, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw); + if (r == 0) { + if (pw) { + if (ret) + *ret = TAKE_PTR(buf); + return 0; + } + + return -ESRCH; + } + + assert(r > 0); + + if (errno_is_user_doesnt_exist(r)) + return -ESRCH; + if (r != ERANGE) + return -r; + + if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd))) + return -ENOMEM; + bufsize *= 2; + } +} + +static size_t getgr_buffer_size(void) { + long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); + return bufsize <= 0 ? 4096U : (size_t) bufsize; +} + +int getgrnam_malloc(const char *name, struct group **ret) { + size_t bufsize = getgr_buffer_size(); + int r; + + if (isempty(name)) + return -EINVAL; + + for (;;) { + _cleanup_free_ void *buf = NULL; + + buf = malloc(ALIGN(sizeof(struct group)) + bufsize); + if (!buf) + return -ENOMEM; + + struct group *gr = NULL; + r = getgrnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr); + if (r == 0) { + if (gr) { + if (ret) + *ret = TAKE_PTR(buf); + return 0; + } + + return -ESRCH; + } + + assert(r > 0); + + if (errno_is_user_doesnt_exist(r)) + return -ESRCH; + if (r != ERANGE) + return -r; + + if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group))) + return -ENOMEM; + bufsize *= 2; + } +} + +int getgrgid_malloc(gid_t gid, struct group **ret) { + size_t bufsize = getgr_buffer_size(); + int r; + + if (!gid_is_valid(gid)) + return -EINVAL; + + for (;;) { + _cleanup_free_ void *buf = NULL; + + buf = malloc(ALIGN(sizeof(struct group)) + bufsize); + if (!buf) + return -ENOMEM; + + struct group *gr = NULL; + r = getgrgid_r(gid, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr); + if (r == 0) { + if (gr) { + if (ret) + *ret = TAKE_PTR(buf); + return 0; + } + + return -ESRCH; + } + + assert(r > 0); + + if (errno_is_user_doesnt_exist(r)) + return -ESRCH; + if (r != ERANGE) + return -r; + + if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group))) + return -ENOMEM; + bufsize *= 2; + } +} diff --git a/src/libnm-systemd-shared/src/basic/utf8.c b/src/libnm-systemd-shared/src/basic/utf8.c index 2a9da59881..633d86c6cf 100644 --- a/src/libnm-systemd-shared/src/basic/utf8.c +++ b/src/libnm-systemd-shared/src/basic/utf8.c @@ -609,3 +609,28 @@ size_t utf8_console_width(const char *str) { return n; } + +size_t utf8_last_length(const char *s, size_t n) { + int r; + + assert(s); + + if (n == SIZE_MAX) + n = strlen(s); + + /* Determines length in bytes of last UTF-8 codepoint in string. If the string is empty, returns + * zero. Treats invalid UTF-8 codepoints as 1 sized ones. */ + + for (size_t last = 0;;) { + if (n == 0) + return last; + + r = utf8_encoded_valid_unichar(s, n); + if (r <= 0) + r = 1; /* treat invalid UTF-8 as byte-wide */ + + s += r; + n -= r; + last = r; + } +} diff --git a/src/libnm-systemd-shared/src/basic/utf8.h b/src/libnm-systemd-shared/src/basic/utf8.h index 221bc46a2d..3b1a468c4f 100644 --- a/src/libnm-systemd-shared/src/basic/utf8.h +++ b/src/libnm-systemd-shared/src/basic/utf8.h @@ -7,7 +7,6 @@ #include #include "macro.h" -#include "missing_type.h" #define UTF8_REPLACEMENT_CHARACTER "\xef\xbf\xbd" #define UTF8_BYTE_ORDER_MARK "\xef\xbb\xbf" @@ -62,3 +61,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); + +size_t utf8_last_length(const char *s, size_t n); diff --git a/src/libnm-systemd-shared/src/fundamental/assert-fundamental.h b/src/libnm-systemd-shared/src/fundamental/assert-fundamental.h new file mode 100644 index 0000000000..0b5c7f21de --- /dev/null +++ b/src/libnm-systemd-shared/src/fundamental/assert-fundamental.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#if !SD_BOOT +# include +#endif + +#include "macro-fundamental.h" + +#if SD_BOOT + _noreturn_ void efi_assert(const char *expr, const char *file, unsigned line, const char *function); + + #ifdef NDEBUG + #define assert(expr) ({ if (!(expr)) __builtin_unreachable(); }) + #define assert_not_reached() __builtin_unreachable() + #else + #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); }) + #define assert_not_reached() efi_assert("Code should not be reached", __FILE__, __LINE__, __func__) + #endif + #define assert_se(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); }) +#else + +_noreturn_ void log_assert_failed(const char *text, const char *file, int line, const char *func); +_noreturn_ void log_assert_failed_unreachable(const char *file, int line, const char *func); + +#ifdef __COVERITY__ + +/* Use special definitions of assertion macros in order to prevent + * false positives of ASSERT_SIDE_EFFECT on Coverity static analyzer + * for uses of assert_se() and assert_return(). + * + * These definitions make expression go through a (trivial) function + * call to ensure they are not discarded. Also use ! or !! to ensure + * the boolean expressions are seen as such. + * + * This technique has been described and recommended in: + * https://community.synopsys.com/s/question/0D534000046Yuzb/suppressing-assertsideeffect-for-functions-that-allow-for-sideeffects + */ + +extern void __coverity_panic__(void); + +static inline void __coverity_check__(int condition) { + if (!condition) + __coverity_panic__(); +} + +static inline int __coverity_check_and_return__(int condition) { + return condition; +} + +#define assert_message_se(expr, message) __coverity_check__(!!(expr)) + +#define assert_log(expr, message) __coverity_check_and_return__(!!(expr)) + +#else /* ! __COVERITY__ */ + +#define assert_message_se(expr, message) \ + do { \ + if (_unlikely_(!(expr))) \ + log_assert_failed(message, PROJECT_FILE, __LINE__, __func__); \ + } while (false) + +#endif /* __COVERITY__ */ + +#define assert_se(expr) assert_message_se(expr, #expr) + +/* We override the glibc assert() here. */ +#undef assert +#ifdef NDEBUG +#define assert(expr) ({ if (!(expr)) __builtin_unreachable(); }) +#else +#define assert(expr) assert_message_se(expr, #expr) +#endif + +#define assert_not_reached() \ + log_assert_failed_unreachable(PROJECT_FILE, __LINE__, __func__) + +#endif + +/* This passes the argument through after (if asserts are enabled) checking that it is not null. */ +#define ASSERT_PTR(expr) _ASSERT_PTR(expr, UNIQ_T(_expr_, UNIQ), assert) +#define ASSERT_SE_PTR(expr) _ASSERT_PTR(expr, UNIQ_T(_expr_, UNIQ), assert_se) +#define _ASSERT_PTR(expr, var, check) \ + ({ \ + typeof(expr) var = (expr); \ + check(var); \ + var; \ + }) + +#define ASSERT_NONNEG(expr) \ + ({ \ + typeof(expr) _expr_ = (expr), _zero = 0; \ + assert(_expr_ >= _zero); \ + _expr_; \ + }) + +#define ASSERT_SE_NONNEG(expr) \ + ({ \ + typeof(expr) _expr_ = (expr), _zero = 0; \ + assert_se(_expr_ >= _zero); \ + _expr_; \ + }) diff --git a/src/libnm-systemd-shared/src/fundamental/iovec-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/iovec-util-fundamental.h index 68d5bf4ee0..4c86197e6f 100644 --- a/src/libnm-systemd-shared/src/fundamental/iovec-util-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/iovec-util-fundamental.h @@ -1,6 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#if !SD_BOOT +#include +#include +#endif + +#include "assert-fundamental.h" +#include "macro-fundamental.h" + #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 { @@ -8,7 +16,9 @@ struct iovec { size_t iov_len; }; -static inline void free(void *p); +DISABLE_WARNING_REDUNDANT_DECLS; +void free(void *p); +REENABLE_WARNING; #endif /* This accepts both const and non-const pointers */ diff --git a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h index 5eb31cd742..dbf0a7d846 100644 --- a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h @@ -1,16 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#if !SD_BOOT -# include -#endif - #include #include #include #include #include +/* This header unconditionally defines MAX() so include it here already so + * it won't override our own definition of MAX() that we define later in this + * file. */ +#if !SD_BOOT +#include +#endif + /* Temporarily disable some warnings */ #define DISABLE_WARNING_DEPRECATED_DECLARATIONS \ _Pragma("GCC diagnostic push"); \ @@ -52,6 +55,10 @@ _Pragma("GCC diagnostic push"); \ _Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"") +#define DISABLE_WARNING_REDUNDANT_DECLS \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wredundant-decls\"") + #if HAVE_WARNING_ZERO_LENGTH_BOUNDS # define DISABLE_WARNING_ZERO_LENGTH_BOUNDS \ _Pragma("GCC diagnostic push"); \ @@ -112,9 +119,19 @@ # define _fallthrough_ __attribute__((__fallthrough__)) #endif +#if __GNUC__ >= 15 +# define _nonnull_if_nonzero_(p, n) __attribute__((nonnull_if_nonzero(p, n))) +#else +# define _nonnull_if_nonzero_(p, n) +#endif + #define XSTRINGIFY(x) #x #define STRINGIFY(x) XSTRINGIFY(x) +/* C23 changed char8_t from char to unsigned char, hence we cannot pass u8 literals to e.g. fputs() without + * casting. Let's introduce our own way to declare UTF-8 literals, which casts u8 literals to const char*. */ +#define UTF8(s) ((const char*) (u8"" s)) + #ifndef __COVERITY__ # define VOID_0 ((void)0) #else @@ -130,45 +147,7 @@ #define XCONCATENATE(x, y) x ## y #define CONCATENATE(x, y) XCONCATENATE(x, y) -#if SD_BOOT - _noreturn_ void efi_assert(const char *expr, const char *file, unsigned line, const char *function); - - #ifdef NDEBUG - #define assert(expr) ({ if (!(expr)) __builtin_unreachable(); }) - #define assert_not_reached() __builtin_unreachable() - #else - #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); }) - #define assert_not_reached() efi_assert("Code should not be reached", __FILE__, __LINE__, __func__) - #endif - #define static_assert _Static_assert - #define assert_se(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); }) -#endif - -/* This passes the argument through after (if asserts are enabled) checking that it is not null. */ -#define ASSERT_PTR(expr) _ASSERT_PTR(expr, UNIQ_T(_expr_, UNIQ), assert) -#define ASSERT_SE_PTR(expr) _ASSERT_PTR(expr, UNIQ_T(_expr_, UNIQ), assert_se) -#define _ASSERT_PTR(expr, var, check) \ - ({ \ - typeof(expr) var = (expr); \ - check(var); \ - var; \ - }) - -#define ASSERT_NONNEG(expr) \ - ({ \ - typeof(expr) _expr_ = (expr), _zero = 0; \ - assert(_expr_ >= _zero); \ - _expr_; \ - }) - -#define ASSERT_SE_NONNEG(expr) \ - ({ \ - typeof(expr) _expr_ = (expr), _zero = 0; \ - assert_se(_expr_ >= _zero); \ - _expr_; \ - }) - -#define assert_cc(expr) static_assert(expr, #expr) +#define assert_cc(expr) _Static_assert(expr, #expr) #define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq)) #define UNIQ __COUNTER__ @@ -391,7 +370,7 @@ _found = true; \ break; \ default: \ - break; \ + ; \ } \ _found; \ }) @@ -424,82 +403,6 @@ (typeof(memory)) NULL; \ }) -static inline size_t ALIGN_TO(size_t l, size_t ali) { - assert(ISPOWEROF2(ali)); - - if (l > SIZE_MAX - (ali - 1)) - return SIZE_MAX; /* indicate overflow */ - - return ((l + (ali - 1)) & ~(ali - 1)); -} - -static inline uint64_t ALIGN_TO_U64(uint64_t l, uint64_t ali) { - assert(ISPOWEROF2(ali)); - - if (l > UINT64_MAX - (ali - 1)) - return UINT64_MAX; /* indicate overflow */ - - return ((l + (ali - 1)) & ~(ali - 1)); -} - -static inline size_t ALIGN_DOWN(size_t l, size_t ali) { - assert(ISPOWEROF2(ali)); - - return l & ~(ali - 1); -} - -static inline uint64_t ALIGN_DOWN_U64(uint64_t l, uint64_t ali) { - assert(ISPOWEROF2(ali)); - - return l & ~(ali - 1); -} - -static inline size_t ALIGN_OFFSET(size_t l, size_t ali) { - assert(ISPOWEROF2(ali)); - - return l & (ali - 1); -} - -static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { - assert(ISPOWEROF2(ali)); - - return l & (ali - 1); -} - -#define ALIGN2(l) ALIGN_TO(l, 2) -#define ALIGN4(l) ALIGN_TO(l, 4) -#define ALIGN8(l) ALIGN_TO(l, 8) -#define ALIGN2_PTR(p) ((void*) ALIGN2((uintptr_t) p)) -#define ALIGN4_PTR(p) ((void*) ALIGN4((uintptr_t) p)) -#define ALIGN8_PTR(p) ((void*) ALIGN8((uintptr_t) p)) -#define ALIGN(l) ALIGN_TO(l, sizeof(void*)) -#define ALIGN_PTR(p) ((void*) ALIGN((uintptr_t) (p))) - -/* Checks if the specified pointer is aligned as appropriate for the specific type */ -#define IS_ALIGNED16(p) (((uintptr_t) p) % alignof(uint16_t) == 0) -#define IS_ALIGNED32(p) (((uintptr_t) p) % alignof(uint32_t) == 0) -#define IS_ALIGNED64(p) (((uintptr_t) p) % alignof(uint64_t) == 0) - -/* Same as ALIGN_TO but callable in constant contexts. */ -#define CONST_ALIGN_TO(l, ali) \ - __builtin_choose_expr( \ - __builtin_constant_p(l) && \ - __builtin_constant_p(ali) && \ - CONST_ISPOWEROF2(ali) && \ - (l <= SIZE_MAX - (ali - 1)), /* overflow? */ \ - ((l) + (ali) - 1) & ~((ali) - 1), \ - VOID_0) - -/* Similar to ((t *) (void *) (p)) to cast a pointer. The macro asserts that the pointer has a suitable - * alignment for type "t". This exists for places where otherwise "-Wcast-align=strict" would issue a - * warning or if you want to assert that the cast gives a pointer of suitable alignment. */ -#define CAST_ALIGN_PTR(t, p) \ - ({ \ - const void *_p = (p); \ - assert(((uintptr_t) _p) % alignof(t) == 0); \ - (t *) _p; \ - }) - #define UPDATE_FLAG(orig, flag, b) \ ((b) ? ((orig) | (flag)) : ((orig) & ~(flag))) #define SET_FLAG(v, flag, b) \ @@ -507,41 +410,11 @@ static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { #define FLAGS_SET(v, flags) \ ((~(v) & (flags)) == 0) -/* A wrapper for 'func' to return void. - * Only useful when a void-returning function is required by some API. */ -#define DEFINE_TRIVIAL_DESTRUCTOR(name, type, func) \ - static inline void name(type *p) { \ - func(p); \ - } +typedef struct { + int _empty[0]; +} dummy_t; -/* When func() returns the void value (NULL, -1, …) of the appropriate type */ -#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ - static inline void func##p(type *p) { \ - if (*p) \ - *p = func(*p); \ - } - -/* When func() doesn't return the appropriate type, set variable to empty afterwards. - * The func() may be provided by a dynamically loaded shared library, hence add an assertion. */ -#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(type, func, empty) \ - static inline void func##p(type *p) { \ - if (*p != (empty)) { \ - DISABLE_WARNING_ADDRESS; \ - assert(func); \ - REENABLE_WARNING; \ - func(*p); \ - *p = (empty); \ - } \ - } - -/* When func() doesn't return the appropriate type, and is also a macro, set variable to empty afterwards. */ -#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(type, func, empty) \ - static inline void func##p(type *p) { \ - if (*p != (empty)) { \ - func(*p); \ - *p = (empty); \ - } \ - } +assert_cc(sizeof(dummy_t) == 0); /* Restriction/bug (see below) was fixed in GCC 15 and clang 19. */ #if __GNUC__ >= 15 || (defined(__clang__) && __clang_major__ >= 19) @@ -590,3 +463,6 @@ static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { #define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) #define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +assert_cc(STRLEN(__FILE__) > STRLEN(RELATIVE_SOURCE_PATH) + 1); +#define PROJECT_FILE (&__FILE__[STRLEN(RELATIVE_SOURCE_PATH) + 1]) diff --git a/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h index 6870f54f58..4b50714f5e 100644 --- a/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h @@ -9,6 +9,7 @@ # include #endif +#include "assert-fundamental.h" #include "macro-fundamental.h" #define memzero(x, l) \ @@ -17,7 +18,7 @@ _l_ > 0 ? memset((x), 0, _l_) : (x); \ }) -#if !SD_BOOT && HAVE_EXPLICIT_BZERO +#if !SD_BOOT static inline void *explicit_bzero_safe(void *p, size_t l) { if (p && l > 0) explicit_bzero(p, l); @@ -106,3 +107,115 @@ static inline void array_cleanup(const ArrayCleanup *c) { _f; \ }), \ } + +/* A wrapper for 'func' to return void. + * Only useful when a void-returning function is required by some API. */ +#define DEFINE_TRIVIAL_DESTRUCTOR(name, type, func) \ + static inline void name(type *p) { \ + func(p); \ + } + +/* When func() returns the void value (NULL, -1, …) of the appropriate type */ +#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ + static inline void func##p(type *p) { \ + if (*p) \ + *p = func(*p); \ + } + +/* When func() doesn't return the appropriate type, set variable to empty afterwards. + * The func() may be provided by a dynamically loaded shared library, hence add an assertion. */ +#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(type, func, empty) \ + static inline void func##p(type *p) { \ + if (*p != (empty)) { \ + DISABLE_WARNING_ADDRESS; \ + assert(func); \ + REENABLE_WARNING; \ + func(*p); \ + *p = (empty); \ + } \ + } + +/* When func() doesn't return the appropriate type, and is also a macro, set variable to empty afterwards. */ +#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(type, func, empty) \ + static inline void func##p(type *p) { \ + if (*p != (empty)) { \ + func(*p); \ + *p = (empty); \ + } \ + } + +static inline size_t ALIGN_TO(size_t l, size_t ali) { + assert(ISPOWEROF2(ali)); + + if (l > SIZE_MAX - (ali - 1)) + return SIZE_MAX; /* indicate overflow */ + + return ((l + (ali - 1)) & ~(ali - 1)); +} + +static inline uint64_t ALIGN_TO_U64(uint64_t l, uint64_t ali) { + assert(ISPOWEROF2(ali)); + + if (l > UINT64_MAX - (ali - 1)) + return UINT64_MAX; /* indicate overflow */ + + return ((l + (ali - 1)) & ~(ali - 1)); +} + +static inline size_t ALIGN_DOWN(size_t l, size_t ali) { + assert(ISPOWEROF2(ali)); + + return l & ~(ali - 1); +} + +static inline uint64_t ALIGN_DOWN_U64(uint64_t l, uint64_t ali) { + assert(ISPOWEROF2(ali)); + + return l & ~(ali - 1); +} + +static inline size_t ALIGN_OFFSET(size_t l, size_t ali) { + assert(ISPOWEROF2(ali)); + + return l & (ali - 1); +} + +static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { + assert(ISPOWEROF2(ali)); + + return l & (ali - 1); +} + +#define ALIGN2(l) ALIGN_TO(l, 2) +#define ALIGN4(l) ALIGN_TO(l, 4) +#define ALIGN8(l) ALIGN_TO(l, 8) +#define ALIGN2_PTR(p) ((void*) ALIGN2((uintptr_t) p)) +#define ALIGN4_PTR(p) ((void*) ALIGN4((uintptr_t) p)) +#define ALIGN8_PTR(p) ((void*) ALIGN8((uintptr_t) p)) +#define ALIGN(l) ALIGN_TO(l, sizeof(void*)) +#define ALIGN_PTR(p) ((void*) ALIGN((uintptr_t) (p))) + +/* Checks if the specified pointer is aligned as appropriate for the specific type */ +#define IS_ALIGNED16(p) (((uintptr_t) p) % alignof(uint16_t) == 0) +#define IS_ALIGNED32(p) (((uintptr_t) p) % alignof(uint32_t) == 0) +#define IS_ALIGNED64(p) (((uintptr_t) p) % alignof(uint64_t) == 0) + +/* Same as ALIGN_TO but callable in constant contexts. */ +#define CONST_ALIGN_TO(l, ali) \ + __builtin_choose_expr( \ + __builtin_constant_p(l) && \ + __builtin_constant_p(ali) && \ + CONST_ISPOWEROF2(ali) && \ + (l <= SIZE_MAX - (ali - 1)), /* overflow? */ \ + ((l) + (ali) - 1) & ~((ali) - 1), \ + VOID_0) + +/* Similar to ((t *) (void *) (p)) to cast a pointer. The macro asserts that the pointer has a suitable + * alignment for type "t". This exists for places where otherwise "-Wcast-align=strict" would issue a + * warning or if you want to assert that the cast gives a pointer of suitable alignment. */ +#define CAST_ALIGN_PTR(t, p) \ + ({ \ + const void *_p = (p); \ + assert(((uintptr_t) _p) % alignof(t) == 0); \ + (t *) _p; \ + }) diff --git a/src/libnm-systemd-shared/src/fundamental/sha256-fundamental.c b/src/libnm-systemd-shared/src/fundamental/sha256-fundamental.c index 03381835d6..108c43b45a 100644 --- a/src/libnm-systemd-shared/src/fundamental/sha256-fundamental.c +++ b/src/libnm-systemd-shared/src/fundamental/sha256-fundamental.c @@ -21,6 +21,13 @@ License along with the GNU C Library; if not, see . */ +#if SD_BOOT +# include "efi-string.h" +#else +# include +#endif + +#include "assert-fundamental.h" #include "macro-fundamental.h" #include "memory-util-fundamental.h" #include "sha256-fundamental.h" diff --git a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c index a18b2bc4c9..c2b58889db 100644 --- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c +++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c @@ -82,7 +82,7 @@ static bool is_valid_version_char(sd_char a) { int strverscmp_improved(const sd_char *a, const sd_char *b) { /* This function is similar to strverscmp(3), but it treats '-' and '.' as separators. * - * The logic is based on rpm's rpmvercmp(), but unlike rpmvercmp(), it distiguishes e.g. + * The logic is based on rpm's rpmvercmp(), but unlike rpmvercmp(), it distinguishes e.g. * '123a' and '123.a', with '123a' being newer. * * It allows direct comparison of strings which contain both a version and a release; e.g. diff --git a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h index 419f1cc3da..a9638b4d0e 100644 --- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h +++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h @@ -8,6 +8,7 @@ # include #endif +#include "assert-fundamental.h" #include "macro-fundamental.h" #if SD_BOOT @@ -16,6 +17,8 @@ # define strncmp strncmp16 # define strcasecmp strcasecmp16 # define strncasecmp strncasecmp16 +# define strspn strspn16 +# define strcspn strcspn16 # define STR_C(str) (L ## str) typedef char16_t sd_char; #else diff --git a/src/libnm-systemd-shared/src/shared/dns-domain.c b/src/libnm-systemd-shared/src/shared/dns-domain.c index e91284177c..a310438b3c 100644 --- a/src/libnm-systemd-shared/src/shared/dns-domain.c +++ b/src/libnm-systemd-shared/src/shared/dns-domain.c @@ -13,6 +13,7 @@ #include "hostname-util.h" #include "idn-util.h" #include "in-addr-util.h" +#include "log.h" #include "macro.h" #include "parse-util.h" #include "string-util.h" @@ -924,9 +925,12 @@ int dns_name_from_wire_format(const uint8_t **data, size_t *len, char **ret) { const char *label; uint8_t c; - /* Unterminated name */ + /* RFC 4704 § 4: fully qualified domain names include the terminating + * zero-length label, partial names don't. According to the RFC, DHCPv6 + * servers should always send the fully qualified name, but that's not + * true in practice. Also accept partial names. */ if (optlen == 0) - return -EBADMSG; + break; /* RFC 1035 § 3.1 total length of encoded name is limited to 255 octets */ if (*len - optlen > 255) @@ -1383,7 +1387,7 @@ int dns_name_apply_idna(const char *name, char **ret) { r = sym_idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t, IDN2_NFC_INPUT | IDN2_TRANSITIONAL); - log_debug("idn2_lookup_u8: %s %s %s", name, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), t); + log_debug("idn2_lookup_u8: %s %s %s", name, glyph(GLYPH_ARROW_RIGHT), t); if (r == IDN2_OK) { if (!startswith(name, "xn--")) { _cleanup_free_ char *s = NULL; @@ -1398,8 +1402,8 @@ int dns_name_apply_idna(const char *name, char **ret) { if (!streq_ptr(name, s)) { log_debug("idn2 roundtrip failed: \"%s\" %s \"%s\" %s \"%s\", ignoring.", - name, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), t, - special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), s); + name, glyph(GLYPH_ARROW_RIGHT), t, + glyph(GLYPH_ARROW_RIGHT), s); *ret = NULL; return 0; } From 872e626342a91a548af9412b9f3c05ab36047daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Bene=C5=A1?= Date: Fri, 30 May 2025 11:36:41 +0200 Subject: [PATCH 02/78] release: bump version to 1.55.0 (development) --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 66474f6b25..1ee1fe35f9 100644 --- a/meson.build +++ b/meson.build @@ -5,7 +5,7 @@ project( # NOTE: When incrementing version also add corresponding # NM_VERSION_x_y_z macros in # "src/libnm-core-public/nm-version-macros.h.in" - version: '1.53.90', + version: '1.55.0', license: 'GPL2+', default_options: [ 'buildtype=debugoptimized', From 897eed184a4c79d75e2f04706e2f66d158d20d4e Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Wed, 20 Nov 2024 02:22:02 -0500 Subject: [PATCH 03/78] build: only demand an xsltproc program when it is used It's intrinsically used when building manpages, and also part of the introspection-specific parts of the documentation build. There's no particular guarantee either of those will actually be invoked during a build, so don't unconditionally look it up. This allows building with one fewer dependency in many cases. --- docs/api/meson.build | 2 ++ man/meson.build | 2 +- meson.build | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/api/meson.build b/docs/api/meson.build index 6dc4adc17b..aea7e5a9a6 100644 --- a/docs/api/meson.build +++ b/docs/api/meson.build @@ -1,6 +1,8 @@ # SPDX-License-Identifier: LGPL-2.1-or-later if enable_introspection + xsltproc = find_program('xsltproc') + settings = 'settings-spec' output = settings + '.xml' diff --git a/man/meson.build b/man/meson.build index 56f52bb4b3..502c36b08e 100644 --- a/man/meson.build +++ b/man/meson.build @@ -7,7 +7,7 @@ common_ent_file = configure_file( ) xsltproc_options = [ - xsltproc, + find_program('xsltproc'), '--output', '@OUTPUT@', '--path', meson.current_build_dir(), '--xinclude', diff --git a/meson.build b/meson.build index 1ee1fe35f9..078a13013c 100644 --- a/meson.build +++ b/meson.build @@ -89,7 +89,6 @@ po_dir = source_root / 'po' top_inc = include_directories('.') perl = find_program('perl') -xsltproc = find_program('xsltproc') check_exports = find_program(join_paths(source_root, 'tools', 'check-exports.sh')) From ee078be7457d0180f1e3a1650625f7461e7c06e8 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Wed, 20 Nov 2024 02:24:50 -0500 Subject: [PATCH 04/78] build: remove outdated, commented-out configure.ac snippet This was likely added during the port to meson. It's just a multiline string, so it has no effect in meson, and it appears to be entirely for things which are already implemented in meson, so there's no real point in keeping it any longer. --- meson.build | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/meson.build b/meson.build index 078a13013c..05fa16811a 100644 --- a/meson.build +++ b/meson.build @@ -963,38 +963,6 @@ data_conf.set('nmstatedir', nm_pkgstatedir) data_conf.set('sbindir', nm_sbindir) data_conf.set('sysconfdir', nm_sysconfdir) -# check if we can build setting property documentation -''' -build_docs=no -if test -n "$INTROSPECTION_MAKEFILE"; then - # If g-i is installed we know we have python, but we might not have pygobject - if ! "$PYTHON" -c 'from gi.repository import GObject' >& /dev/null; then - AC_MSG_ERROR(["--enable-introspection aims to build the settings documentation. This requires GObject introspection for python (pygobject)]) - fi - - AC_PATH_PROG(PERL, perl) - if test -z "$PERL"; then - AC_MSG_ERROR([--enable-introspection requires perl]) - fi - AC_PATH_PROG(XSLTPROC, xsltproc) - if test -z "$XSLTPROC"; then - AC_MSG_ERROR([--enable-introspection requires xsltproc]) - fi - - have_introspection=yes - if test "$enable_gtk_doc" = "yes"; then - build_docs=yes - fi -else - if test "$enable_gtk_doc" = "yes"; then - # large parts of the documentation require introspection/pygobject to extract - # the documentation out of the source files. You cannot enable gtk-doc without alone. - AC_MSG_ERROR(["--with-gtk-doc requires --enable-introspection"]) - fi - have_introspection=no -fi -''' - content_files = [] subdir('introspection') From f656675b57e26763f4afa1c4549d56ba75cbc049 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Mon, 18 Nov 2024 16:35:37 -0500 Subject: [PATCH 05/78] build: install pre-disted manpages if available When building from a release tarball of NetworkManager, `meson.add_dist_script()` has copied various prebuilt manpages into the tarball and building them again is not really necessary. We can just install those directly. This means that *all* manpages could be installed even without introspection, even though some of them can only be *built* when introspection is enabled. It also means that manpages can be installed even when xsltproc is not available at build time. --- man/meson.build | 67 ++++++++++++++++++++++++++++++++++--------------- meson.build | 3 ++- 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/man/meson.build b/man/meson.build index 502c36b08e..5a8e520429 100644 --- a/man/meson.build +++ b/man/meson.build @@ -1,5 +1,52 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +mans = [ + ['NetworkManager', '8'], + ['NetworkManager-dispatcher', '8'], + ['NetworkManager-wait-online.service', '8'], + ['NetworkManager.conf', '5'], + ['nm-online', '1'], + ['nmcli-examples', '7'], + ['nmcli', '1'], + ['nmtui', '1'], + ['nm-initrd-generator', '8'], +] + +if enable_ovs + mans += [['nm-openvswitch', '7']] +endif + +if enable_nm_cloud_setup + mans += [['nm-cloud-setup', '8']] +endif + +introspection_mans = [ + ['nm-settings-keyfile', '5'], + ['nm-settings-dbus', '5'], + ['nm-settings-nmcli', '5'], +] + +if enable_ifcfg_rh + introspection_mans += [['nm-settings-ifcfg-rh', '5']] +endif + +built_mans = [] +foreach man: mans + introspection_mans + name = man[0] + '.' + man[1] + if not fs.exists(name) + built_mans = [] + break + endif + + built_mans += name +endforeach + +if built_mans.length() > 0 + install_man(built_mans) + subdir_done() +endif + + common_ent_file = configure_file( input: 'common.ent.in', output: '@BASENAME@', @@ -24,26 +71,6 @@ docbook_xls = 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbo mans_xmls = [] -mans = [ - ['NetworkManager', '8'], - ['NetworkManager-dispatcher', '8'], - ['NetworkManager-wait-online.service', '8'], - ['NetworkManager.conf', '5'], - ['nm-online', '1'], - ['nmcli-examples', '7'], - ['nmcli', '1'], - ['nmtui', '1'], - ['nm-initrd-generator', '8'], -] - -if enable_ovs - mans += [['nm-openvswitch', '7']] -endif - -if enable_nm_cloud_setup - mans += [['nm-cloud-setup', '8']] -endif - foreach man: mans input = man[0] + '.xml' content_files += join_paths(meson.current_source_dir(), input) diff --git a/meson.build b/meson.build index 05fa16811a..c03d98c15b 100644 --- a/meson.build +++ b/meson.build @@ -12,7 +12,7 @@ project( 'c_std=gnu11', 'warning_level=2' # value "2" will add "-Wall" and "-Wextra" to the compiler flags ], - meson_version: '>= 0.51.0', + meson_version: '>= 0.53.0', ) nm_name = meson.project_name() @@ -77,6 +77,7 @@ libnm_version = '@0@.@1@.@2@'.format(current - age, age, revision) libnm_pkgincludedir = join_paths(nm_includedir, libnm_name) +fs = import('fs') gnome = import('gnome') i18n = import('i18n') pkg = import('pkgconfig') From a11760ef39629fd5f850b9a495e5b155c2cb120d Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Sun, 10 Nov 2024 17:59:55 -0500 Subject: [PATCH 06/78] build: make manpages a mandatory part of the build by default Currently, both man pages and gtk-doc HTML documentation are enabled by the same build option. It is common for users to want to choose whether to build HTML docs, as not everyone cares about HTML developer docs, but manpages are intended directly for end-user consumption and should always be available. At the very least, there should be a separate option to disable them to avoid accidentally disabling them while trying to disable HTML developer docs. Resolves: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/1653 --- man/meson.build | 105 +++++++++++++++++++----------------- meson.build | 10 +++- meson_options.txt | 1 + tools/meson-post-install.sh | 5 +- 4 files changed, 68 insertions(+), 53 deletions(-) diff --git a/man/meson.build b/man/meson.build index 5a8e520429..d3310dc1de 100644 --- a/man/meson.build +++ b/man/meson.build @@ -41,54 +41,53 @@ foreach man: mans + introspection_mans built_mans += name endforeach -if built_mans.length() > 0 - install_man(built_mans) - subdir_done() +if enable_introspection or enable_docs + common_ent_file = configure_file( + input: 'common.ent.in', + output: '@BASENAME@', + configuration: data_conf, + ) endif +if enable_introspection and (enable_man or enable_docs) + xsltproc_options = [ + find_program('xsltproc'), + '--output', '@OUTPUT@', + '--path', meson.current_build_dir(), + '--xinclude', + '--nonet', + '--stringparam', 'man.output.quietly', '1', + '--stringparam', 'funcsynopsis.style', 'ansi', + '--stringparam', 'man.th.extra1.suppress', '1', + '--stringparam', 'man.authors.section.enabled', '0', + '--stringparam', 'man.copyright.section.enabled', '0', + '--stringparam', 'man.th.title.max.length', '30', + ] -common_ent_file = configure_file( - input: 'common.ent.in', - output: '@BASENAME@', - configuration: data_conf, -) + docbook_xls = 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl' -xsltproc_options = [ - find_program('xsltproc'), - '--output', '@OUTPUT@', - '--path', meson.current_build_dir(), - '--xinclude', - '--nonet', - '--stringparam', 'man.output.quietly', '1', - '--stringparam', 'funcsynopsis.style', 'ansi', - '--stringparam', 'man.th.extra1.suppress', '1', - '--stringparam', 'man.authors.section.enabled', '0', - '--stringparam', 'man.copyright.section.enabled', '0', - '--stringparam', 'man.th.title.max.length', '30', -] + mans_xmls = [] -docbook_xls = 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl' + foreach man: mans + input = man[0] + '.xml' + content_files += join_paths(meson.current_source_dir(), input) -mans_xmls = [] + output = '@0@.@1@'.format(man[0], man[1]) -foreach man: mans - input = man[0] + '.xml' - content_files += join_paths(meson.current_source_dir(), input) + # not needed if only html requested + if enable_man + custom_target( + output, + input: input, + output: output, + command: xsltproc_options + [docbook_xls, '@INPUT@'], + depend_files: common_ent_file, + install: true, + install_dir: join_paths(nm_mandir, 'man' + man[1]), + ) + endif + endforeach - output = '@0@.@1@'.format(man[0], man[1]) - - custom_target( - output, - input: input, - output: output, - command: xsltproc_options + [docbook_xls, '@INPUT@'], - depend_files: common_ent_file, - install: true, - install_dir: join_paths(nm_mandir, 'man' + man[1]), - ) -endforeach - -if enable_introspection merge_cmd = files(source_root / 'tools' / 'generate-docs-nm-settings-docs-merge.py') name = 'dbus' @@ -151,13 +150,23 @@ if enable_introspection output = '@0@.@1@'.format(man[0], man[1]) - custom_target( - output, - input: input, - output: output, - command: xsltproc_options + [docbook_xls, '@INPUT@'], - install: true, - install_dir: join_paths(nm_mandir, 'man' + man[1]), - ) + # not needed if only html requested + if enable_man + custom_target( + output, + input: input, + output: output, + command: xsltproc_options + [docbook_xls, '@INPUT@'], + install: true, + install_dir: join_paths(nm_mandir, 'man' + man[1]), + ) + endif endforeach +# not needed if only html requested +elif enable_man + if built_mans.length() > 0 + install_man(built_mans) + else + error('Building manpages requires xsltproc and -Dintrospection=true, and no prebuilt manpages were found. Try building from a release tarball or using -Dman=false.') + endif endif diff --git a/meson.build b/meson.build index c03d98c15b..1e28a72650 100644 --- a/meson.build +++ b/meson.build @@ -816,6 +816,7 @@ if enable_nm_cloud_setup assert(jansson_dep.found(), 'nm-cloud-setup requires jansson library. Use -Dnm_cloud_setup=false to disable it') endif +enable_man = get_option('man') enable_docs = get_option('docs') more_asserts = get_option('more_asserts') @@ -1001,9 +1002,14 @@ if enable_qt != 'false' endif endif +# The man/ directory builds a couple targets needed by the docs build too. +# If we build with docs but no man, then enter the subdir and only build +# some targets. +if enable_docs or enable_man + subdir('man') +endif if enable_docs assert(enable_introspection, '-Ddocs=true requires -Dintrospection=true') - subdir('man') subdir('docs') meson.add_dist_script( 'tools/meson-dist-data.sh', @@ -1054,7 +1060,7 @@ meson.add_install_script( nm_pkgstatedir, nm_mandir, nm_sysconfdir, - enable_docs ? '1' : '0', + enable_man ? '1' : '0', enable_ifcfg_rh ? '1' : '0', enable_nm_cloud_setup ? '1' : '0', install_systemdunitdir ? '1' : '0', diff --git a/meson_options.txt b/meson_options.txt index 18d8bfc44c..55762f65d5 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -67,6 +67,7 @@ option('config_dhcp_default', type: 'combo', choices: ['dhclient', 'dhcpcd', 'in option('introspection', type: 'boolean', value: true, description: 'Enable introspection for this build') option('vapi', type : 'combo', choices : ['auto', 'true', 'false'], description: 'build Vala bindings') option('docs', type: 'boolean', value: false, description: 'use to build documentation') +option('man', type: 'boolean', value: true, description: 'Install manpages') option('tests', type: 'combo', choices: ['yes', 'no', 'root'], value: 'yes', description: 'Build NetworkManager tests') option('firewalld_zone', type: 'boolean', value: true, description: 'Install and use firewalld zone for shared mode') option('more_asserts', type: 'string', value: 'auto', description: 'Enable more assertions for debugging (0 = no, 100 = all, default: auto)') diff --git a/tools/meson-post-install.sh b/tools/meson-post-install.sh index fe85d417ae..2560e3d16d 100755 --- a/tools/meson-post-install.sh +++ b/tools/meson-post-install.sh @@ -7,7 +7,7 @@ nm_pkglibdir="$4" nm_pkgstatedir="$5" nm_mandir="$6" nm_sysconfdir="$7" -enable_docs="$8" +enable_man="$8" enable_ifcfg_rh="$9" enable_nm_cloud_setup="${10}" install_systemdunitdir="${11}" @@ -40,7 +40,7 @@ done mkdir -p "${DESTDIR}${nm_pkgstatedir}" chmod 0700 "${DESTDIR}${nm_pkgstatedir}" -if [ "$enable_docs" = 1 ]; then +if [ "$enable_man" = 1 ]; then for alias in nmtui-connect nmtui-edit nmtui-hostname; do ln -fn "${DESTDIR}${nm_mandir}/man1/nmtui.1" "${DESTDIR}${nm_mandir}/man1/${alias}.1" @@ -58,4 +58,3 @@ if [ "$enable_nm_cloud_setup" = 1 -a "$install_systemdunitdir" = 1 ]; then ln -sfn '../pre-up.d/90-nm-cloud-setup.sh' "${DESTDIR}${nm_pkglibdir}/dispatcher.d/no-wait.d/90-nm-cloud-setup.sh" ln -sfn 'no-wait.d/90-nm-cloud-setup.sh' "${DESTDIR}${nm_pkglibdir}/dispatcher.d/90-nm-cloud-setup.sh" fi - From fe62ce6a4b8edae64b30d1bb54602a0c48c7f8f1 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Wed, 19 Feb 2025 16:43:54 -0500 Subject: [PATCH 07/78] ci: avoid building manpages on Fedora It appears that xsltproc is not installed there. It is generally needed for the docs build too, so as a crude CI heuristic, don't build anywhere that docs are disabled. --- contrib/scripts/nm-ci-run.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/scripts/nm-ci-run.sh b/contrib/scripts/nm-ci-run.sh index 4881d110d3..ff9aadf898 100755 --- a/contrib/scripts/nm-ci-run.sh +++ b/contrib/scripts/nm-ci-run.sh @@ -167,6 +167,7 @@ meson setup build \ -D libpsl=false \ -D vapi=false \ -D introspection=$_WITH_DOCS \ + -D man=$_WITH_DOCS \ -D qt=false \ -D crypto=$_WITH_CRYPTO \ -D docs=$_WITH_DOCS \ From 4be97207ed322480863dd1734e393a4f205f5499 Mon Sep 17 00:00:00 2001 From: Jan Vaclav Date: Wed, 28 May 2025 13:26:04 +0200 Subject: [PATCH 08/78] tools/nm-in-container: update package install list for fedora 42 - ausearch is part of audit package - mlocate is not available anymore - "openvswitch2*" packages are not available anymore - ipsec-tools is not available anymore --- tools/nm-in-container | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/nm-in-container b/tools/nm-in-container index 365b8fcf8d..93ba59ce2b 100755 --- a/tools/nm-in-container +++ b/tools/nm-in-container @@ -189,7 +189,7 @@ RUN dnf install -y \\ --skip-broken \\ \\ /usr/bin/python \\ - /usr/sbin/ausearch \\ + audit \\ ModemManager-devel \\ ModemManager-glib-devel \\ NetworkManager \\ @@ -231,7 +231,7 @@ RUN dnf install -y \\ libubsan \\ libuuid-devel \\ meson \\ - mlocate \\ + plocate \\ mobile-broadband-provider-info-devel \\ newt-devel \\ nispor \\ @@ -267,7 +267,7 @@ RUN dnf install -y \\ which \\ \\ 'dbus*' \\ - 'openvswitch2*' \\ + 'openvswitch*' \\ /usr/bin/pytest \\ /usr/bin/debuginfo-install \\ NetworkManager-openvpn \\ @@ -290,7 +290,6 @@ RUN dnf install -y \\ git \\ hostapd \\ iproute-tc \\ - ipsec-tools \\ iputils \\ iscsi-initiator-utils \\ iw \\ From eac9f828e8189d4ddfaa5f9437445d2c0231b5ed Mon Sep 17 00:00:00 2001 From: Jan Vaclav Date: Wed, 28 May 2025 13:26:34 +0200 Subject: [PATCH 09/78] tools/nm-in-container: add --skip-unavailable flag for installing debuginfo --- tools/nm-in-container | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/nm-in-container b/tools/nm-in-container index 93ba59ce2b..5fe122d7af 100755 --- a/tools/nm-in-container +++ b/tools/nm-in-container @@ -321,7 +321,7 @@ RUN dnf install -y \\ wireguard-tools \\ wireshark-cli -RUN dnf debuginfo-install --skip-broken NetworkManager \$(ldd /usr/sbin/NetworkManager | sed -n 's/.* => \\(.*\\) (0x[0-9A-Fa-f]*)$/\1/p' | xargs -n1 readlink -f) -y +RUN dnf debuginfo-install --skip-unavailable --skip-broken NetworkManager \$(ldd /usr/sbin/NetworkManager | sed -n 's/.* => \\(.*\\) (0x[0-9A-Fa-f]*)$/\1/p' | xargs -n1 readlink -f) -y RUN dnf clean all From b00c6749d7d64fae25d71b46561ed2591b3adbeb Mon Sep 17 00:00:00 2001 From: Conn O'Griofa Date: Sun, 11 May 2025 23:21:30 +0100 Subject: [PATCH 10/78] core: fix WPA2 fallback for WPA3 transition APs When connecting to an AP configured for WPA3 transition mode, the connection will fail if PMF is disabled on the client due to SAE and FT-SAE being unconditionally added to the key_mgmt variable's parameters. By removing the "!is_ap ||" check, SAE and FT-SAE will no longer be selected when PMF is disabled, allowing clients to connect via WPA2/PSK mode as per the original intent of a0988868ba7b4390790cab43cca5103f80a6a300. Signed-off-by: Conn O'Griofa --- src/core/supplicant/nm-supplicant-config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/supplicant/nm-supplicant-config.c b/src/core/supplicant/nm-supplicant-config.c index 9e06639510..38294e89a3 100644 --- a/src/core/supplicant/nm-supplicant-config.c +++ b/src/core/supplicant/nm-supplicant-config.c @@ -1012,7 +1012,7 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig if (_get_capability(priv, NM_SUPPL_CAP_TYPE_SAE) && _get_capability(priv, NM_SUPPL_CAP_TYPE_PMF) && _get_capability(priv, NM_SUPPL_CAP_TYPE_BIP) - && (!is_ap || pmf != NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE)) { + && (pmf != NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE)) { g_string_append(key_mgmt_conf, " SAE"); if (!is_ap && _get_capability(priv, NM_SUPPL_CAP_TYPE_FT)) g_string_append(key_mgmt_conf, " FT-SAE"); From 46e0d2b4e4eb5948db12186a3c60d3fd98ae8cd4 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 4 Jun 2025 10:57:51 +0200 Subject: [PATCH 11/78] ovs: set the tun interface up before stage3 When using the netdev datapath, we wait that the tun link appears, we call nm_device_set_ip_ifindex() (which also brings the link up) and then we check that the link is ready, i.e. that udev has announced the link and the MAC address is correct. After that, we schedule stage3 (ip-config). In this, there is a race condition that occurs sometimes in NMCI test ovs_datapath_type_netdev_with_cloned_mac. In rare conditions, nm_device_set_ip_ifindex() bring the interface up but then ovs-vswitch changes again the flags of the interface without IFF_UP. The result is that the interface stays down, breaking communications. To fix this, we need to always call nm_device_bring_up() after the tun device is ready. The problem is that we can't do it in _netdev_tun_link_cb() because that function is already invoked synchronously from platform code. Instead, simplify the handling of the netdev datapath. Every "link-changed" event from platform is handled by _netdev_tun_link_cb(), which always schedule a delayed function _netdev_tun_link_cb_in_idle(). This function just assigns the ip-ifindex to the device if missing, and starts stage3 if the link is ready. While doing so, it also bring the interface up. Fixes: 99a6c6eda6e1 ('ovs, dpdk: fix creating ovs-interface when the ovs-bridge is netdev') https://issues.redhat.com/browse/RHEL-17358 https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2218 --- .../devices/ovs/nm-device-ovs-interface.c | 98 ++++++++----------- 1 file changed, 41 insertions(+), 57 deletions(-) diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c index 271dedeab3..922ed34571 100644 --- a/src/core/devices/ovs/nm-device-ovs-interface.c +++ b/src/core/devices/ovs/nm-device-ovs-interface.c @@ -28,16 +28,18 @@ typedef struct { NMOvsdb *ovsdb; struct { - /* The source for the idle handler to set the TUN ifindex */ - GSource *tun_set_ifindex_idle_source; + /* The signal id for the TUN link-changed event */ + gulong tun_link_signal_id; + /* The idle handler source for the TUN link-changed event */ + GSource *tun_link_idle_source; + /* The ifindex for the TUN link-changed event */ + int tun_ifindex; + /* The cloned MAC to set */ char *cloned_mac; - /* The id for the signal watching the TUN link to appear/change */ - gulong tun_link_signal_id; - /* The TUN ifindex to set in the idle handler */ - int tun_ifindex; /* Whether we have determined the cloned MAC */ bool cloned_mac_evaluated : 1; + /* Whether we are waiting for the kernel link */ bool waiting : 1; } wait_link; @@ -263,39 +265,33 @@ ready_for_ip_config(NMDevice *device, gboolean is_manual) } static gboolean -_set_ip_ifindex_tun(gpointer user_data) +_netdev_tun_link_cb_in_idle(gpointer user_data) { NMDevice *device = user_data; NMDeviceOvsInterface *self = NM_DEVICE_OVS_INTERFACE(device); NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self); - _LOGT(LOGD_CORE, - "ovs-wait-link: setting ip-ifindex %d from tun interface", - priv->wait_link.tun_ifindex); + if (nm_device_get_ip_ifindex(device) <= 0) { + _LOGT(LOGD_CORE, + "ovs-wait-link: setting ip-ifindex %d from tun link", + priv->wait_link.tun_ifindex); + nm_device_set_ip_ifindex(device, priv->wait_link.tun_ifindex); + } - nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source); - - nm_device_set_ip_ifindex(device, priv->wait_link.tun_ifindex); - - if (check_waiting_for_link(device, "set-ip-ifindex-tun")) { - /* If the link is not ready, it means the MAC is not set yet. We don't have - * a convenient way to monitor for ip-ifindex changes other than listening - * for platform events again.*/ - nm_assert(!priv->wait_link.tun_link_signal_id); - priv->wait_link.tun_link_signal_id = g_signal_connect(nm_device_get_platform(device), - NM_PLATFORM_SIGNAL_LINK_CHANGED, - G_CALLBACK(_netdev_tun_link_cb), - self); + if (check_waiting_for_link(device, "tun-link-changed")) { + nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source); return G_SOURCE_CONTINUE; } - _LOGT(LOGD_CORE, "tun link is ready"); - + _LOGT(LOGD_CORE, "ovs-wait-link: tun link is ready"); nm_device_link_properties_set(device, FALSE); + nm_device_bring_up(device); nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL); nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL); nm_device_activate_schedule_stage3_ip_config(device, FALSE); + nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id); + nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source); return G_SOURCE_CONTINUE; } @@ -311,40 +307,28 @@ _netdev_tun_link_cb(NMPlatform *platform, const NMPlatformSignalChangeType change_type = change_type_i; NMDeviceOvsInterface *self = NM_DEVICE_OVS_INTERFACE(device); NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self); - int ip_ifindex; + /* This is the handler for the link-changed platform events. It is triggered for all + * link changes. Keep only the ones matching our device. */ + if (!NM_IN_SET(change_type, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_SIGNAL_CHANGED)) + return; if (pllink->type != NM_LINK_TYPE_TUN || !nm_streq0(pllink->name, nm_device_get_iface(device))) return; - ip_ifindex = nm_device_get_ip_ifindex(device); - if (ip_ifindex > 0) { - /* When we have an ifindex, we are only waiting for the MAC to settle */ - if (change_type != NM_PLATFORM_SIGNAL_CHANGED) - return; - - if (!check_waiting_for_link(device, "tun-link-changed")) { - _LOGT(LOGD_CORE, "ovs-wait-link: tun link is ready, cloned MAC is set"); - - nm_clear_g_signal_handler(platform, &priv->wait_link.tun_link_signal_id); - nm_device_link_properties_set(device, FALSE); - - nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL); - nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL); - nm_device_activate_schedule_stage3_ip_config(device, FALSE); - } - return; - } - - /* No ip-ifindex on the device, set it when the link appears */ - if (change_type != NM_PLATFORM_SIGNAL_ADDED) - return; - _LOGT(LOGD_CORE, - "ovs-wait-link: found matching tun interface, schedule set-ip-ifindex(%d)", + "ovs-wait-link: got platform event \'%s\' for ifindex %d, scheduling idle handler", + change_type == NM_PLATFORM_SIGNAL_ADDED ? "added" : "changed", ifindex); - nm_clear_g_signal_handler(platform, &priv->wait_link.tun_link_signal_id); - priv->wait_link.tun_ifindex = ifindex; - priv->wait_link.tun_set_ifindex_idle_source = nm_g_idle_add_source(_set_ip_ifindex_tun, device); + + /* The handler is invoked by the platform synchronously in the netlink receive loop. + * We can't perform other platform operations (like bringing the interface up) since + * the code there is not re-entrant. Schedule an idle handler. */ + nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source); + priv->wait_link.tun_link_idle_source = + nm_g_idle_add_source(_netdev_tun_link_cb_in_idle, device); + priv->wait_link.tun_ifindex = ifindex; + + return; } static gboolean @@ -466,7 +450,7 @@ act_stage3_ip_config(NMDevice *device, int addr_family) nm_device_activate_schedule_stage3_ip_config(device, TRUE); return; } - nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source); + nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source); nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id); nm_device_link_properties_set(device, FALSE); @@ -490,7 +474,7 @@ deactivate(NMDevice *device) priv->wait_link.cloned_mac_evaluated = FALSE; nm_clear_g_free(&priv->wait_link.cloned_mac); nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id); - nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source); + nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source); } typedef struct { @@ -583,7 +567,7 @@ deactivate_async(NMDevice *device, _LOGT(LOGD_CORE, "deactivate: start async"); nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id); - nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source); + nm_clear_g_source_inst(&priv->wait_link.tun_link_idle_source); priv->wait_link.tun_ifindex = -1; priv->wait_link.cloned_mac_evaluated = FALSE; nm_clear_g_free(&priv->wait_link.cloned_mac); @@ -706,7 +690,7 @@ dispose(GObject *object) nm_assert(!priv->wait_link.waiting); nm_assert(priv->wait_link.tun_link_signal_id == 0); - nm_assert(!priv->wait_link.tun_set_ifindex_idle_source); + nm_assert(!priv->wait_link.tun_link_idle_source); if (priv->ovsdb) { g_signal_handlers_disconnect_by_func(priv->ovsdb, G_CALLBACK(ovsdb_ready), self); From f13aca1aba5ff294568b1a40e68637e76c35e489 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 16 Jun 2025 10:23:25 +0200 Subject: [PATCH 12/78] NEWS: update --- NEWS | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 02387856a9..901a8bb9cb 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ ============================================= -NetworkManager-1.54 -Overview of changes since NetworkManager-1.52 +NetworkManager-1.56 +Overview of changes since NetworkManager-1.54 ============================================= This is a snapshot of NetworkManager development. The API is @@ -8,6 +8,12 @@ subject to change and not guaranteed to be compatible with the later release. USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! + +============================================= +NetworkManager-1.54 +Overview of changes since NetworkManager-1.52 +============================================= + * Add systemd services to provide networking in the initrd. * Introduce a new "ovs-dpdk.lsc-interrupt" property to configure the Link State Change (LSC) detection mode for OVS DPDK interfaces. From b4dde691ec297b79bc1efc2d87533a41d909f8a7 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 5 Jun 2025 17:53:50 +0200 Subject: [PATCH 13/78] nmcli: add support for managing wireguard peers Even if WireGuard is supported since long time in NetworkManager, it is still not possible to manage the list of peers via nmcli. The reason is that in the past we wanted to introduce a special syntax that would allow to manage the peer list more easily. However, this requires heavy changes to the nmcli output formatting code, and so it never happened. Since perfection is the enemy of good, abandon the idea of a custom handling of peers and treat them as any other composite property. The property is named "wireguard.peers" and exposes the peers indexed by public key, with optional attributes. Example: $ nmcli connection modify wg0 wireguard.peers "8Wgc1a0jJX3rQULwD5NFFLKrKQnbOnTiaNoerLneG1o= preshared-key=16uGwZvROnwyNGoW6Z3pvJB5GKbd6ncYROA/FFleLQA= allowed-ips=0.0.0.0/0 persistent-keepalive=10" $ nmcli connection modify wg0 +wireguard.peers "fd2NSxUjkaR/Jft15+gpXU13hKSyZLoe4cp+g+feBCc= allowed-ips=192.168.40.0/24 endpoint=172.25.10.1:8888" $ nmcli -g wireguard.peers connection show wg0 8Wgc1a0jJX3rQULwD5NFFLKrKQnbOnTiaNoerLneG1o= allowed-ips=0.0.0.0/0 persistent-keepalive=10, fd2NSxUjkaR/Jft15+gpXU13hKSyZLoe4cp+g+feBCc= allowed-ips=192.168.40.0/24 endpoint=172.25.10.1\:8888 $ nmcli connection modify wg0 -wireguard.peers 8Wgc1a0jJX3rQULwD5NFFLKrKQnbOnTiaNoerLneG1o= $ nmcli -g wireguard.peers connection show wg0 fd2NSxUjkaR/Jft15+gpXU13hKSyZLoe4cp+g+feBCc= allowed-ips=192.168.40.0/24 endpoint=172.25.10.1\:8888 --- NEWS | 1 + .../tests/test-libnm-client-aux.c | 222 ++++++++++++++++++ src/libnm-core-aux-extern/nm-libnm-core-aux.c | 175 ++++++++++++++ src/libnm-core-aux-extern/nm-libnm-core-aux.h | 6 +- src/libnm-core-impl/nm-setting-wireguard.c | 67 ++++++ src/libnmc-setting/nm-meta-setting-desc.c | 59 +++++ src/libnmc-setting/settings-docs.h.in | 1 + .../gen-metadata-nm-settings-nmcli.xml.in | 3 + 8 files changed, 533 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 901a8bb9cb..9a248c7024 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ subject to change and not guaranteed to be compatible with the later release. USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! +* nmcli now supports viewing and managing WireGuard peers. ============================================= NetworkManager-1.54 diff --git a/src/libnm-client-aux-extern/tests/test-libnm-client-aux.c b/src/libnm-client-aux-extern/tests/test-libnm-client-aux.c index f727b3d93e..6a25b652d1 100644 --- a/src/libnm-client-aux-extern/tests/test-libnm-client-aux.c +++ b/src/libnm-client-aux-extern/tests/test-libnm-client-aux.c @@ -234,6 +234,227 @@ test_team_link_watcher_tofro_string(void) NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE); } +static void +test_wireguard_peer(void) +{ + guint i; + struct { + const char *input; + const char *canonical; /* canonical string representation */ + + gboolean invalid; + const char *pubkey; + const char *endpoint; + guint16 keepalive; + guint num_allowed_ips; + const char *allowed_ips[2]; + const char *psk; + int psk_flags; + } tests[] = {{ + /* Public key only */ + .input = "MWEKYcE9MEh5RoGDuJYrJ2YgkoosONGhuHRBAC00e14=", + .canonical = "MWEKYcE9MEh5RoGDuJYrJ2YgkoosONGhuHRBAC00e14=", + .pubkey = "MWEKYcE9MEh5RoGDuJYrJ2YgkoosONGhuHRBAC00e14=", + }, + { + /* IPv4 endpoint */ + .input = "+DIX0qWKQ4E6hy7MWzsSRXjqAHCtffWrXTdJPe/xS04=" + " endpoint=1.2.3.4:5555", + .canonical = "+DIX0qWKQ4E6hy7MWzsSRXjqAHCtffWrXTdJPe/xS04=" + " endpoint=1.2.3.4:5555", + .pubkey = "+DIX0qWKQ4E6hy7MWzsSRXjqAHCtffWrXTdJPe/xS04=", + .endpoint = "1.2.3.4:5555", + }, + { + /* IPv6 endpoint */ + .input = "aPsdPkeqH4l5Nax3g3e8A8f7g0hJk2l3m4N5p6q7R8s=" + " endpoint=[fd01:db8::1]:8080", + .canonical = "aPsdPkeqH4l5Nax3g3e8A8f7g0hJk2l3m4N5p6q7R8s=" + " endpoint=[fd01:db8::1]:8080", + .pubkey = "aPsdPkeqH4l5Nax3g3e8A8f7g0hJk2l3m4N5p6q7R8s=", + .endpoint = "[fd01:db8::1]:8080", + }, + { + /* IPv6 endpoint, without brackets */ + .input = "+DIX0qWKQ4E6hy7MWzsSRXjqAHCtffWrXTdJPe/xS04=" + " endpoint=fd01::12:8080", + .canonical = "+DIX0qWKQ4E6hy7MWzsSRXjqAHCtffWrXTdJPe/xS04=" + " endpoint=fd01::12:8080", + .pubkey = "+DIX0qWKQ4E6hy7MWzsSRXjqAHCtffWrXTdJPe/xS04=", + .endpoint = "fd01::12:8080", + }, + { + /* Single IPv4 allowed-ip */ + .input = "s4fmZZA3gMGVv8+0hkSwrmeLC6nNd+Pd6DlSaufLKhY=" + " allowed-ips=172.16.0.0/16", + .canonical = "s4fmZZA3gMGVv8+0hkSwrmeLC6nNd+Pd6DlSaufLKhY=" + " allowed-ips=172.16.0.0/16", + .pubkey = "s4fmZZA3gMGVv8+0hkSwrmeLC6nNd+Pd6DlSaufLKhY=", + .num_allowed_ips = 1, + .allowed_ips = {"172.16.0.0/16"}, + }, + { + /* Multiple allowed-ips */ + .input = "V02J2zmCi2LHX2KMK+ZOgDNhZzK4JXjGNr7CYfz9DxQ=" + " allowed-ips=192.168.2.0/24;2001:db8:a::/48", + .canonical = "V02J2zmCi2LHX2KMK+ZOgDNhZzK4JXjGNr7CYfz9DxQ=" + " allowed-ips=192.168.2.0/24;2001:db8:a::/48", + .pubkey = "V02J2zmCi2LHX2KMK+ZOgDNhZzK4JXjGNr7CYfz9DxQ=", + .num_allowed_ips = 2, + .allowed_ips = {"192.168.2.0/24", "2001:db8:a::/48"}, + }, + { + /* Persistent-keepalive */ + .input = "D1FTp8Wy1oJQI045yXo9EMdxJqjXHC3VhTCPTh3lSQM=" + " persistent-keepalive=25", + .canonical = "D1FTp8Wy1oJQI045yXo9EMdxJqjXHC3VhTCPTh3lSQM=" + " persistent-keepalive=25", + .pubkey = "D1FTp8Wy1oJQI045yXo9EMdxJqjXHC3VhTCPTh3lSQM=", + .keepalive = 25, + }, + { + /* Preshared-key without flags (should default to 0) */ + .input = "H5cWWgpWgJH+nHFhsuPS3adgZHuc6Z4cRzfiNRTinE0=" + " preshared-key=16uGwZvROnwyNGoW6Z3pvJB5GKbd6ncYROA/FFleLQA=", + .canonical = "H5cWWgpWgJH+nHFhsuPS3adgZHuc6Z4cRzfiNRTinE0=" + " preshared-key=16uGwZvROnwyNGoW6Z3pvJB5GKbd6ncYROA/FFleLQA=" + " preshared-key-flags=0", + .pubkey = "H5cWWgpWgJH+nHFhsuPS3adgZHuc6Z4cRzfiNRTinE0=", + .psk = "16uGwZvROnwyNGoW6Z3pvJB5GKbd6ncYROA/FFleLQA=", + .psk_flags = 0, + }, + { + /* Preshared-key flags as string */ + .input = "H5cWWgpWgJH+nHFhsuPS3adgZHuc6Z4cRzfiNRTinE0=" + " preshared-key=16uGwZvROnwyNGoW6Z3pvJB5GKbd6ncYROA/FFleLQA=" + " preshared-key-flags=not-saved", + .canonical = "H5cWWgpWgJH+nHFhsuPS3adgZHuc6Z4cRzfiNRTinE0=" + " preshared-key=16uGwZvROnwyNGoW6Z3pvJB5GKbd6ncYROA/FFleLQA=" + " preshared-key-flags=2", + .pubkey = "H5cWWgpWgJH+nHFhsuPS3adgZHuc6Z4cRzfiNRTinE0=", + .psk = "16uGwZvROnwyNGoW6Z3pvJB5GKbd6ncYROA/FFleLQA=", + .psk_flags = 2, + }, + { + /* Non-canonical order and extra whitespaces */ + .input = "gqQ9dUqKQNfz/KOqELJpS0MKBvRcYWL8sm/LGEWKKQY=" + " preshared-key=EVVP8pOzn8R3nQtv62/hnGsXzyagEgykSboFe4EFhQc=" + " endpoint=vpn.example.com:51820 " + " preshared-key-flags=1" + " persistent-keepalive=45" + " allowed-ips=0.0.0.0/0;::/0", + .canonical = "gqQ9dUqKQNfz/KOqELJpS0MKBvRcYWL8sm/LGEWKKQY=" + " allowed-ips=0.0.0.0/0;::/0" + " endpoint=vpn.example.com:51820" + " persistent-keepalive=45" + " preshared-key=EVVP8pOzn8R3nQtv62/hnGsXzyagEgykSboFe4EFhQc=" + " preshared-key-flags=1", + .pubkey = "gqQ9dUqKQNfz/KOqELJpS0MKBvRcYWL8sm/LGEWKKQY=", + .endpoint = "vpn.example.com:51820", + .keepalive = 45, + .num_allowed_ips = 2, + .allowed_ips = {"0.0.0.0/0", "::/0"}, + .psk = "EVVP8pOzn8R3nQtv62/hnGsXzyagEgykSboFe4EFhQc=", + .psk_flags = 1, + }, + { + /* Empty string */ + .input = "", + .invalid = TRUE, + }, + { + /* Invalid public key*/ + .input = "aaaaaaaaaaaaaaaaaaaaaaa=", + .invalid = TRUE, + }, + { + /* Missing value*/ + .input = "gqQ9dUqKQNfz/KOqELJpS0MKBvRcYWL8sm/LGEWKKQY= " + "persistent-keepalive=", + .invalid = TRUE, + }, + { + /* Unknown attribute */ + .input = "gqQ9dUqKQNfz/KOqELJpS0MKBvRcYWL8sm/LGEWKKQY= " + "persistent-keepalive=12 foobarness=13", + .invalid = TRUE, + }, + { + /* Invalid IPv4 allowed-ip*/ + .input = "gqQ9dUqKQNfz/KOqELJpS0MKBvRcYWL8sm/LGEWKKQY= " + "allowed-ips=192.168.10.256/32", + .invalid = TRUE, + }, + { + /* Invalid IPv6 allowed-ip */ + .input = "gqQ9dUqKQNfz/KOqELJpS0MKBvRcYWL8sm/LGEWKKQY= " + "allowed-ips=fd01::1::3/64", + .invalid = TRUE, + }, + { + /* Endpoint with no port */ + .input = "+DIX0qWKQ4E6hy7MWzsSRXjqAHCtffWrXTdJPe/xS04=" + " endpoint=1.2.3.4", + .invalid = TRUE, + }, + { + /* Invalid endpoint */ + .input = "+DIX0qWKQ4E6hy7MWzsSRXjqAHCtffWrXTdJPe/xS04=" + " endpoint=1.2.3.5.6", + .invalid = TRUE, + }, + { + /* Invalid persistent-keepalive */ + .input = "gqQ9dUqKQNfz/KOqELJpS0MKBvRcYWL8sm/LGEWKKQY= " + "persistent-keepalive=yes", + .invalid = TRUE, + }, + { + /* Invalid PSK */ + .input = "gqQ9dUqKQNfz/KOqELJpS0MKBvRcYWL8sm/LGEWKKQY=" + " preshared-key=pskpskpskpskpskpskpskpskpskpskpskpsk", + .invalid = TRUE, + }}; + + for (i = 0; i < G_N_ELEMENTS(tests); i++) { + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + gs_free_error GError *error = NULL; + gs_free char *newstr = NULL; + guint j; + + peer = _nm_utils_wireguard_peer_from_string(tests[i].input, &error); + if (tests[i].invalid) { + g_assert(!peer); + g_assert(error); + continue; + } + g_assert_no_error(error); + g_assert_nonnull(peer); + + newstr = _nm_utils_wireguard_peer_to_string(peer); + g_assert_nonnull(newstr); + g_assert_cmpstr(tests[i].canonical, ==, newstr); + + g_assert_cmpstr(tests[i].pubkey, ==, nm_wireguard_peer_get_public_key(peer)); + g_assert_cmpstr(tests[i].endpoint, ==, nm_wireguard_peer_get_endpoint(peer)); + + g_assert_cmpint(tests[i].num_allowed_ips, ==, nm_wireguard_peer_get_allowed_ips_len(peer)); + for (j = 0; j < tests[i].num_allowed_ips; j++) { + g_assert_cmpstr(tests[i].allowed_ips[j], + ==, + nm_wireguard_peer_get_allowed_ip(peer, j, NULL)); + } + + g_assert_cmpint(tests[i].keepalive, ==, nm_wireguard_peer_get_persistent_keepalive(peer)); + g_assert_cmpstr(tests[i].psk, ==, nm_wireguard_peer_get_preshared_key(peer)); + if (tests[i].psk) { + g_assert_cmpint(tests[i].psk_flags, + ==, + nm_wireguard_peer_get_preshared_key_flags(peer)); + } + } +} + /*****************************************************************************/ NMTST_DEFINE(); @@ -245,6 +466,7 @@ main(int argc, char **argv) g_test_add_func("/libnm-core-aux/test_team_link_watcher_tofro_string", test_team_link_watcher_tofro_string); + g_test_add_func("/libnm-core-aux/test-wireguard-peer", test_wireguard_peer); return g_test_run(); } diff --git a/src/libnm-core-aux-extern/nm-libnm-core-aux.c b/src/libnm-core-aux-extern/nm-libnm-core-aux.c index dfc70e35b4..b12000fc14 100644 --- a/src/libnm-core-aux-extern/nm-libnm-core-aux.c +++ b/src/libnm-core-aux-extern/nm-libnm-core-aux.c @@ -7,6 +7,7 @@ #include "nm-libnm-core-aux.h" +#include "nm-errors.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "libnm-glib-aux/nm-str-buf.h" @@ -475,3 +476,177 @@ _nm_ip_route_to_string(NMIPRoute *route, NMStrBuf *strbuf) nm_str_buf_append_printf(strbuf, " metric %" G_GINT64_FORMAT, metric); } } + +/*****************************************************************************/ + +char * +_nm_utils_wireguard_peer_to_string(NMWireGuardPeer *peer) +{ + GString *str; + const char *endpoint; + const char *psk; + guint16 keepalive; + guint i; + guint len; + + g_return_val_if_fail(peer, ""); + + nm_assert(nm_wireguard_peer_is_valid(peer, TRUE, TRUE, NULL)); + + str = g_string_new(""); + g_string_append(str, nm_wireguard_peer_get_public_key(peer)); + + len = nm_wireguard_peer_get_allowed_ips_len(peer); + if (len > 0) { + g_string_append(str, " allowed-ips="); + for (i = 0; i < len; i++) { + g_string_append(str, nm_wireguard_peer_get_allowed_ip(peer, i, NULL)); + if (i < len - 1) + g_string_append(str, ";"); + } + } + + endpoint = nm_wireguard_peer_get_endpoint(peer); + if (endpoint) { + g_string_append_printf(str, " endpoint=%s", endpoint); + } + + keepalive = nm_wireguard_peer_get_persistent_keepalive(peer); + if (keepalive != 0) { + g_string_append_printf(str, " persistent-keepalive=%hu", keepalive); + } + + psk = nm_wireguard_peer_get_preshared_key(peer); + if (psk) { + g_string_append_printf(str, " preshared-key=%s", psk); + g_string_append_printf(str, + " preshared-key-flags=%u", + (guint) nm_wireguard_peer_get_preshared_key_flags(peer)); + } + + return g_string_free(str, FALSE); +} + +NMWireGuardPeer * +_nm_utils_wireguard_peer_from_string(const char *str, GError **error) +{ + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + gs_strfreev char **tokens = NULL; + gboolean has_psk = FALSE; + gboolean has_psk_flags = FALSE; + char *value; + guint i; + + peer = nm_wireguard_peer_new(); + + tokens = g_strsplit_set(str, " ", 0); + for (i = 0; tokens[i]; i++) { + if (i == 0) { + if (!nm_wireguard_peer_set_public_key(peer, tokens[i], FALSE)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "invalid public key '%s'", + tokens[i]); + return NULL; + } + continue; + } + + if (tokens[i][0] == '\0') + continue; + + value = strchr(tokens[i], '='); + if (!value || value[1] == '\0') { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "attribute without value '%s'", + tokens[i]); + return NULL; + } + + *value = '\0'; + value++; + + if (nm_streq(tokens[i], "allowed-ips")) { + gs_strfreev char **ips = NULL; + guint j; + + ips = g_strsplit_set(value, ";", 0); + for (j = 0; ips[j]; j++) { + if (!nm_wireguard_peer_append_allowed_ip(peer, ips[j], FALSE)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "invalid allowed-ip '%s'", + ips[j]); + return NULL; + } + } + } else if (nm_streq(tokens[i], "endpoint")) { + if (!nm_wireguard_peer_set_endpoint(peer, value, FALSE)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "invalid endpoint '%s'", + value); + return NULL; + } + } else if (nm_streq(tokens[i], "persistent-keepalive")) { + gint64 keepalive; + + keepalive = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXUINT16, -1); + if (keepalive == -1) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "invalid persistent-keepalive value '%s'", + value); + return NULL; + } + nm_wireguard_peer_set_persistent_keepalive(peer, (guint16) keepalive); + } else if (nm_streq(tokens[i], "preshared-key")) { + if (!nm_wireguard_peer_set_preshared_key(peer, value, FALSE)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "invalid preshared-key '%s'", + value); + return NULL; + } + has_psk = TRUE; + } else if (nm_streq(tokens[i], "preshared-key-flags")) { + int flags; + + if (!nm_utils_enum_from_str(NM_TYPE_SETTING_SECRET_FLAGS, value, &flags, NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "invalid preshared-key-flags '%s'", + value); + return NULL; + } + nm_wireguard_peer_set_preshared_key_flags(peer, (NMSettingSecretFlags) flags); + has_psk_flags = TRUE; + } else { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "invalid attribute '%s'", + tokens[i]); + return NULL; + } + } + + if (has_psk && !has_psk_flags) { + /* The flags are NOT_REQUIRED by default. With this flag, the PSK would not + * be saved by default, unless the user explicitly sets a different value. */ + nm_wireguard_peer_set_preshared_key_flags(peer, NM_SETTING_SECRET_FLAG_NONE); + } + + if (!nm_wireguard_peer_is_valid(peer, TRUE, TRUE, error)) + return NULL; + + return g_steal_pointer(&peer); +} diff --git a/src/libnm-core-aux-extern/nm-libnm-core-aux.h b/src/libnm-core-aux-extern/nm-libnm-core-aux.h index e45e98ab2d..aa7ea631ef 100644 --- a/src/libnm-core-aux-extern/nm-libnm-core-aux.h +++ b/src/libnm-core-aux-extern/nm-libnm-core-aux.h @@ -6,8 +6,9 @@ #ifndef __NM_LIBNM_CORE_AUX_H__ #define __NM_LIBNM_CORE_AUX_H__ -#include "nm-setting-team.h" #include "nm-setting-ip-config.h" +#include "nm-setting-team.h" +#include "nm-setting-wireguard.h" typedef enum { NM_TEAM_LINK_WATCHER_TYPE_NONE = 0, @@ -39,4 +40,7 @@ void _nm_ip_route_to_string(NMIPRoute *route, struct _NMStrBuf *strbuf); NMTeamLinkWatcher *nm_utils_team_link_watcher_from_string(const char *str, GError **error); +char *_nm_utils_wireguard_peer_to_string(NMWireGuardPeer *peer); +NMWireGuardPeer *_nm_utils_wireguard_peer_from_string(const char *str, GError **error); + #endif /* __NM_LIBNM_CORE_AUX_H__ */ diff --git a/src/libnm-core-impl/nm-setting-wireguard.c b/src/libnm-core-impl/nm-setting-wireguard.c index 668af1f6e0..a03090865f 100644 --- a/src/libnm-core-impl/nm-setting-wireguard.c +++ b/src/libnm-core-impl/nm-setting-wireguard.c @@ -2515,6 +2515,73 @@ nm_setting_wireguard_class_init(NMSettingWireGuardClass *klass) NMSettingWireGuard, _priv.ip6_auto_default_route); + /* ---nmcli--- + * property: peers + * format: a comma-separated list of WireGuard peers + * description: + * A comma-separated list of WireGuard peers. Each peer has the following syntax: + * + * PUBLIC_KEY [ATTRIBUTE=VALUE [ATTRIBUTE=VALUE]...] + * + * The supported attributes are: endpoint, allowed-ips, persistent-keepalive, + * preshared-key, preshared-key-flags. + * description-docbook: + * + * A comma-separated list of WireGuard peers. Each peer has the following syntax: + * + * + * + * public-key + * [attribute=value + * [attribute=value]...] + * + * + * + * The public key is required and must be encoded as base64; it can be + * calculated by running wg pubkey on the private key, + * and it is usually transmitted out of band to the author of the configuration + * file. + * + * + * The supported attributes are: + * + * + * endpoint + * An endpoint IP or hostname, followed by a colon, and then + * a port number. + * + * + * + * + * + * + * + * allowed-ips + * A semicolon-separated list of IP (v4 or v6) addresses + * with CIDR masks from which incoming traffic for this peer is allowed + * and to which outgoing traffic for this peer is directed + * + * + * + * persistent-keepalive + * An interval in seconds, between 1 and 65535, of + * how often to send an authenticated empty packet to the peer for the + * purpose of keeping a stateful firewall or NAT mapping valid persistently. + * + * + * + * preshared-key + * A base64 preshared key generated by "wg genpsk". Optional, + * and may be omitted. + * + * + * preshared-key-flags + * The secret flags for the preshared-key. + * + * + * + * ---end--- + */ /* ---dbus--- * property: peers * format: array of 'a{sv}' diff --git a/src/libnmc-setting/nm-meta-setting-desc.c b/src/libnmc-setting/nm-meta-setting-desc.c index 9b11cd4a17..b025093ada 100644 --- a/src/libnmc-setting/nm-meta-setting-desc.c +++ b/src/libnmc-setting/nm-meta-setting-desc.c @@ -4156,6 +4156,50 @@ _optionlist_set_fcn_vpn_secrets(NMSetting *setting, return TRUE; } +static void +_objlist_obj_to_str_fcn_wireguard_peers(NMMetaAccessorGetType get_type, + NMSetting *setting, + guint idx, + GString *str) +{ + NMWireGuardPeer *peer; + gs_free char *peer_str = NULL; + + peer = nm_setting_wireguard_get_peer(NM_SETTING_WIREGUARD(setting), idx); + peer_str = _nm_utils_wireguard_peer_to_string(peer); + g_string_append(str, peer_str); +} + +static gboolean +_objlist_set_fcn_wireguard_peers(NMSetting *setting, + gboolean do_add, + const char *value, + GError **error) +{ + NMSettingWireGuard *s_wg = NM_SETTING_WIREGUARD(setting); + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + + peer = _nm_utils_wireguard_peer_from_string(value, error); + if (!peer) + return FALSE; + + if (do_add) { + nm_setting_wireguard_append_peer(s_wg, peer); + } else { + NMWireGuardPeer *match; + guint idx; + + match = nm_setting_wireguard_get_peer_by_public_key(s_wg, + nm_wireguard_peer_get_public_key(peer), + &idx); + if (match) { + nm_setting_wireguard_remove_peer(s_wg, idx); + } + } + + return TRUE; +} + static gboolean _set_fcn_wired_s390_subchannels(ARGS_SET_FCN) { @@ -8423,6 +8467,21 @@ static const NMMetaPropertyInfo *const property_infos_WIREGUARD[] = { PROPERTY_INFO_WITH_DESC (NM_SETTING_WIREGUARD_IP6_AUTO_DEFAULT_ROUTE, .property_type = &_pt_gobject_ternary, ), + + PROPERTY_INFO_WITH_DESC (NM_SETTING_WIREGUARD_PEERS, + .property_type = &_pt_objlist, + .property_typ_data = DEFINE_PROPERTY_TYP_DATA ( + PROPERTY_TYP_DATA_SUBTYPE (objlist, + .get_num_fcn = OBJLIST_GET_NUM_FCN (NMSettingWireGuard, nm_setting_wireguard_get_peers_len), + .clear_all_fcn = (void (*) (NMSetting *))(void (*)(void)) nm_setting_wireguard_clear_peers, + .obj_to_str_fcn = _objlist_obj_to_str_fcn_wireguard_peers, + .set_fcn = _objlist_set_fcn_wireguard_peers, + .remove_by_idx_fcn_u = (void (*) (NMSetting *, guint idx))(void (*)(void)) nm_setting_wireguard_remove_peer, + .strsplit_plain = TRUE, + ), + ), + ), + NULL }; diff --git a/src/libnmc-setting/settings-docs.h.in b/src/libnmc-setting/settings-docs.h.in index 814ca19297..8678dc64eb 100644 --- a/src/libnmc-setting/settings-docs.h.in +++ b/src/libnmc-setting/settings-docs.h.in @@ -443,6 +443,7 @@ #define DESCRIBE_DOC_NM_SETTING_WIREGUARD_LISTEN_PORT N_("The listen-port. If listen-port is not specified, the port will be chosen randomly when the interface comes up.") #define DESCRIBE_DOC_NM_SETTING_WIREGUARD_MTU N_("If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple fragments. If zero a default MTU is used. Note that contrary to wg-quick's MTU setting, this does not take into account the current routes at the time of activation.") #define DESCRIBE_DOC_NM_SETTING_WIREGUARD_PEER_ROUTES N_("Whether to automatically add routes for the AllowedIPs ranges of the peers. If TRUE (the default), NetworkManager will automatically add routes in the routing tables according to ipv4.route-table and ipv6.route-table. Usually you want this automatism enabled. If FALSE, no such routes are added automatically. In this case, the user may want to configure static routes in ipv4.routes and ipv6.routes, respectively. Note that if the peer's AllowedIPs is \"0.0.0.0/0\" or \"::/0\" and the profile's ipv4.never-default or ipv6.never-default setting is enabled, the peer route for this peer won't be added automatically.") +#define DESCRIBE_DOC_NM_SETTING_WIREGUARD_PEERS N_("A comma-separated list of WireGuard peers. Each peer has the following syntax: PUBLIC_KEY [ATTRIBUTE=VALUE [ATTRIBUTE=VALUE]...] The supported attributes are: endpoint, allowed-ips, persistent-keepalive, preshared-key, preshared-key-flags.") #define DESCRIBE_DOC_NM_SETTING_WIREGUARD_PRIVATE_KEY N_("The 256 bit private-key in base64 encoding.") #define DESCRIBE_DOC_NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS N_("Flags indicating how to handle the \"private-key\" property.") #define DESCRIBE_DOC_NM_SETTING_WIRELESS_AP_ISOLATION N_("Configures AP isolation, which prevents communication between wireless devices connected to this AP. This property can be set to a value different from \"default\" (-1) only when the interface is configured in AP mode. If set to \"true\" (1), devices are not able to communicate with each other. This increases security because it protects devices against attacks from other clients in the network. At the same time, it prevents devices to access resources on the same wireless networks as file shares, printers, etc. If set to \"false\" (0), devices can talk to each other. When set to \"default\" (-1), the global default is used; in case the global default is unspecified it is assumed to be \"false\" (0).") diff --git a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in index 155c413cb8..957480aea2 100644 --- a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in +++ b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in @@ -2320,6 +2320,9 @@ nmcli-description="Like ip4-auto-default-route, but for the IPv6 default route." format="ternary" values="true/yes/on, false/no/off, default/unknown" /> + Date: Wed, 4 Jun 2025 22:22:06 +0200 Subject: [PATCH 14/78] version: add 1.56 macros --- src/libnm-core-public/nm-version-macros.h.in | 1 + src/libnm-core-public/nm-version.h | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/libnm-core-public/nm-version-macros.h.in b/src/libnm-core-public/nm-version-macros.h.in index 4c4772d293..49f283f6ea 100644 --- a/src/libnm-core-public/nm-version-macros.h.in +++ b/src/libnm-core-public/nm-version-macros.h.in @@ -78,6 +78,7 @@ #define NM_VERSION_1_50 (NM_ENCODE_VERSION(1, 50, 0)) #define NM_VERSION_1_52 (NM_ENCODE_VERSION(1, 52, 0)) #define NM_VERSION_1_54 (NM_ENCODE_VERSION(1, 54, 0)) +#define NM_VERSION_1_56 (NM_ENCODE_VERSION(1, 56, 0)) /* For releases, NM_API_VERSION is equal to NM_VERSION. * diff --git a/src/libnm-core-public/nm-version.h b/src/libnm-core-public/nm-version.h index 33daf65f15..775ed62747 100644 --- a/src/libnm-core-public/nm-version.h +++ b/src/libnm-core-public/nm-version.h @@ -439,6 +439,20 @@ #define NM_AVAILABLE_IN_1_54 #endif +#if NM_VERSION_MIN_REQUIRED >= NM_VERSION_1_56 +#define NM_DEPRECATED_IN_1_56 G_DEPRECATED +#define NM_DEPRECATED_IN_1_56_FOR(f) G_DEPRECATED_FOR(f) +#else +#define NM_DEPRECATED_IN_1_56 +#define NM_DEPRECATED_IN_1_56_FOR(f) +#endif + +#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_56 +#define NM_AVAILABLE_IN_1_56 G_UNAVAILABLE(1, 56) +#else +#define NM_AVAILABLE_IN_1_56 +#endif + /* * Synchronous API for calling D-Bus in libnm is deprecated. See * https://networkmanager.dev/docs/libnm/latest/usage.html#sync-api From eb0a22a162a57658156abceb252b3c115fd645fe Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 19 May 2025 14:15:27 +0200 Subject: [PATCH 15/78] libnm,nmcli: add sriov.preserve-on-down property Add a new "sriov.preserve-on-down" property that controls whether NetworkManager preserves the SR-IOV parameters set on the device when the connection is deactivated, or whether it resets them to their default value. The SR-IOV parameters are those specified in the "sriov" setting, like the number of VFs to create, the eswitch configuration, etc. --- src/libnm-client-impl/libnm.ver | 6 +++ ...gen-metadata-nm-settings-libnm-core.xml.in | 4 ++ src/libnm-core-impl/nm-setting-sriov.c | 47 +++++++++++++++++++ src/libnm-core-public/nm-setting-sriov.h | 19 ++++++++ src/libnmc-setting/nm-meta-setting-desc.c | 3 ++ src/libnmc-setting/settings-docs.h.in | 1 + .../gen-metadata-nm-settings-nmcli.xml.in | 4 ++ 7 files changed, 84 insertions(+) diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver index 6a093a0658..e7ca51da88 100644 --- a/src/libnm-client-impl/libnm.ver +++ b/src/libnm-client-impl/libnm.ver @@ -2074,3 +2074,9 @@ global: nm_setting_prefix_delegation_get_type; nm_setting_prefix_delegation_new; } libnm_1_52_0; + +libnm_1_56_0 { +global: + nm_setting_sriov_get_preserve_on_down; + nm_sriov_preserve_on_down_get_type; +} libnm_1_54_0; diff --git a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in index d165957179..c764f53568 100644 --- a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in +++ b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in @@ -2386,6 +2386,10 @@ dbus-type="i" gprop-type="gint" /> + preserve_on_down; +} + /** * nm_setting_sriov_get_autoprobe_drivers: * @setting: the #NMSettingSriov @@ -1462,6 +1480,35 @@ nm_setting_sriov_class_init(NMSettingSriovClass *klass) NMSettingSriov, eswitch_encap_mode); + /** + * NMSettingSriov:preserve-on-down + * + * This controls whether NetworkManager preserves the SR-IOV parameters set on + * the device when the connection is deactivated, or whether it resets them to + * their default value. The SR-IOV parameters are those specified in this setting + * (the "sriov" setting), like the number of VFs to create, the eswitch + * configuration, etc. + * + * If set to %NM_SRIOV_PRESERVE_ON_DOWN_NO, NetworkManager resets the SR-IOV + * parameters when the connection is deactivated. When set to + * %NM_SRIOV_PRESERVE_ON_DOWN_YES, NetworkManager preserves those parameters + * on the device. If the value is %NM_SRIOV_PRESERVE_ON_DOWN_DEFAULT, NetworkManager + * looks up a global default value in the configuration; in case no such value is + * defined, it uses %NM_SRIOV_PRESERVE_ON_DOWN_NO as fallback. + * + * Since: 1.56 + */ + _nm_setting_property_define_direct_enum(properties_override, + obj_properties, + NM_SETTING_SRIOV_PRESERVE_ON_DOWN, + PROP_PRESERVE_ON_DOWN, + NM_TYPE_SRIOV_PRESERVE_ON_DOWN, + NM_SRIOV_PRESERVE_ON_DOWN_DEFAULT, + NM_SETTING_PARAM_FUZZY_IGNORE, + NULL, + NMSettingSriov, + preserve_on_down); + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); _nm_setting_class_commit(setting_class, diff --git a/src/libnm-core-public/nm-setting-sriov.h b/src/libnm-core-public/nm-setting-sriov.h index affccc4892..e45620fe8a 100644 --- a/src/libnm-core-public/nm-setting-sriov.h +++ b/src/libnm-core-public/nm-setting-sriov.h @@ -28,6 +28,7 @@ G_BEGIN_DECLS #define NM_SETTING_SRIOV_TOTAL_VFS "total-vfs" #define NM_SETTING_SRIOV_VFS "vfs" +#define NM_SETTING_SRIOV_PRESERVE_ON_DOWN "preserve-on-down" #define NM_SETTING_SRIOV_AUTOPROBE_DRIVERS "autoprobe-drivers" #define NM_SETTING_SRIOV_ESWITCH_MODE "eswitch-mode" #define NM_SETTING_SRIOV_ESWITCH_INLINE_MODE "eswitch-inline-mode" @@ -56,6 +57,22 @@ typedef enum { NM_SRIOV_VF_VLAN_PROTOCOL_802_1AD = 1, } NMSriovVFVlanProtocol; +/** + * NMSriovPreserveOnDown: + * @NM_SRIOV_PRESERVE_ON_DOWN_DEFAULT: use the default value + * @NM_SRIOV_PRESERVE_ON_DOWN_NO: reset the SR-IOV parameters when the + * connection is deactivated + * @NM_SRIOV_PRESERVE_ON_DOWN_YES: preserve the SR-IOV parameters set on + * the device when the connection is deactivated + * + * Since: 1.56 + */ +typedef enum { + NM_SRIOV_PRESERVE_ON_DOWN_DEFAULT = -1, + NM_SRIOV_PRESERVE_ON_DOWN_NO = 0, + NM_SRIOV_PRESERVE_ON_DOWN_YES = 1, +} NMSriovPreserveOnDown; + /** * NMSriovEswitchMode: * @NM_SRIOV_ESWITCH_MODE_PRESERVE: don't modify current eswitch mode @@ -123,6 +140,8 @@ NM_AVAILABLE_IN_1_14 gboolean nm_setting_sriov_remove_vf_by_index(NMSettingSriov *setting, guint index); NM_AVAILABLE_IN_1_14 void nm_setting_sriov_clear_vfs(NMSettingSriov *setting); +NM_AVAILABLE_IN_1_56 +NMSriovPreserveOnDown nm_setting_sriov_get_preserve_on_down(NMSettingSriov *setting); NM_AVAILABLE_IN_1_14 NMTernary nm_setting_sriov_get_autoprobe_drivers(NMSettingSriov *setting); NM_AVAILABLE_IN_1_46 diff --git a/src/libnmc-setting/nm-meta-setting-desc.c b/src/libnmc-setting/nm-meta-setting-desc.c index b025093ada..f62830312f 100644 --- a/src/libnmc-setting/nm-meta-setting-desc.c +++ b/src/libnmc-setting/nm-meta-setting-desc.c @@ -7674,6 +7674,9 @@ static const NMMetaPropertyInfo *const property_infos_SRIOV[] = { PROPERTY_INFO_WITH_DESC (NM_SETTING_SRIOV_ESWITCH_ENCAP_MODE, .property_type = &_pt_gobject_enum, ), + PROPERTY_INFO_WITH_DESC (NM_SETTING_SRIOV_PRESERVE_ON_DOWN, + .property_type = &_pt_gobject_enum, + ), NULL }; diff --git a/src/libnmc-setting/settings-docs.h.in b/src/libnmc-setting/settings-docs.h.in index 8678dc64eb..8f7ae31d4e 100644 --- a/src/libnmc-setting/settings-docs.h.in +++ b/src/libnmc-setting/settings-docs.h.in @@ -353,6 +353,7 @@ #define DESCRIBE_DOC_NM_SETTING_SRIOV_ESWITCH_ENCAP_MODE N_("Select the eswitch encapsulation support. Currently it's only supported for PCI PF devices, and only if the eswitch device is managed from the same PCI address than the PF. If set to \"preserve\" (-1) (default) the eswitch encap-mode won't be modified by NetworkManager.") #define DESCRIBE_DOC_NM_SETTING_SRIOV_ESWITCH_INLINE_MODE N_("Select the eswitch inline-mode of the device. Some HWs need the VF driver to put part of the packet headers on the TX descriptor so the e-switch can do proper matching and steering. Currently it's only supported for PCI PF devices, and only if the eswitch device is managed from the same PCI address than the PF. If set to \"preserve\" (-1) (default) the eswitch inline-mode won't be modified by NetworkManager.") #define DESCRIBE_DOC_NM_SETTING_SRIOV_ESWITCH_MODE N_("Select the eswitch mode of the device. Currently it's only supported for PCI PF devices, and only if the eswitch device is managed from the same PCI address than the PF. If set to \"preserve\" (-1) (default) the eswitch mode won't be modified by NetworkManager.") +#define DESCRIBE_DOC_NM_SETTING_SRIOV_PRESERVE_ON_DOWN N_("This controls whether NetworkManager preserves the SR-IOV parameters set on the device when the connection is deactivated, or whether it resets them to their default value. The SR-IOV parameters are those specified in this setting (the \"sriov\" setting), like the number of VFs to create, the eswitch configuration, etc. If set to \"no\" (0), NetworkManager resets the SR-IOV parameters when the connection is deactivated. When set to \"yes\" (1), NetworkManager preserves those parameters on the device. If the value is \"default\" (-1), NetworkManager looks up a global default value in the configuration; in case no such value is defined, it uses \"no\" (0) as fallback.") #define DESCRIBE_DOC_NM_SETTING_SRIOV_TOTAL_VFS N_("The total number of virtual functions to create. Note that when the sriov setting is present NetworkManager enforces the number of virtual functions on the interface (also when it is zero) during activation and resets it upon deactivation. To prevent any changes to SR-IOV parameters don't add a sriov setting to the connection.") #define DESCRIBE_DOC_NM_SETTING_SRIOV_VFS N_("Array of virtual function descriptors. Each VF descriptor is a dictionary mapping attribute names to GVariant values. The 'index' entry is mandatory for each VF. When represented as string a VF is in the form: \"INDEX [ATTR=VALUE[ ATTR=VALUE]...]\". for example: \"2 mac=00:11:22:33:44:55 spoof-check=true\". Multiple VFs can be specified using a comma as separator. Currently, the following attributes are supported: mac, spoof-check, trust, min-tx-rate, max-tx-rate, vlans. The \"vlans\" attribute is represented as a semicolon-separated list of VLAN descriptors, where each descriptor has the form \"ID[.PRIORITY[.PROTO]]\". PROTO can be either 'q' for 802.1Q (the default) or 'ad' for 802.1ad.") #define DESCRIBE_DOC_NM_SETTING_TC_CONFIG_QDISCS N_("Array of TC queueing disciplines. When the \"tc\" setting is present, qdiscs from this property are applied upon activation. If the property is empty, all qdiscs are removed and the device will only have the default qdisc assigned by kernel according to the \"net.core.default_qdisc\" sysctl. If the \"tc\" setting is not present, NetworkManager doesn't touch the qdiscs present on the interface.") diff --git a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in index 957480aea2..c57366d817 100644 --- a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in +++ b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in @@ -1984,6 +1984,10 @@ nmcli-description="Select the eswitch encapsulation support. Currently it's only supported for PCI PF devices, and only if the eswitch device is managed from the same PCI address than the PF. If set to "preserve" (-1) (default) the eswitch encap-mode won't be modified by NetworkManager." format="choice (NMSriovEswitchEncapMode)" values="preserve (-1), none (0), basic (1)" /> + Date: Mon, 19 May 2025 15:39:45 +0200 Subject: [PATCH 16/78] core: use the sriov.preserve-on-down property --- man/NetworkManager.conf.xml | 6 ++++++ src/core/devices/nm-device.c | 26 ++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml index 93e7b016cf..2b42755971 100644 --- a/man/NetworkManager.conf.xml +++ b/man/NetworkManager.conf.xml @@ -1093,6 +1093,12 @@ ipv6.ip6-privacy=0 sriov.autoprobe-drivers If left unspecified, drivers are autoprobed when the SR-IOV VF gets created. + + sriov.preserve-on-down + Set to 0 or 1 to select whether the SR-IOV parameters are + preserved when the connection is deactivated. If left unspecified, default + to 0 (parameters are not preserved). + vpn.timeout If left unspecified, default value of 60 seconds is used. diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 1f68551f1d..74037ca69d 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -1416,6 +1416,26 @@ _prop_get_connection_mdns(NMDevice *self) NM_SETTING_CONNECTION_MDNS_DEFAULT); } +static gboolean +_prop_get_sriov_preserve_on_down(NMDevice *self, NMSettingSriov *s_sriov) +{ + NMSriovPreserveOnDown preserve; + + g_return_val_if_fail(NM_IS_DEVICE(self), FALSE); + g_return_val_if_fail(NM_IS_SETTING_SRIOV(s_sriov), FALSE); + + preserve = nm_setting_sriov_get_preserve_on_down(s_sriov); + if (NM_IN_SET(preserve, NM_SRIOV_PRESERVE_ON_DOWN_NO, NM_SRIOV_PRESERVE_ON_DOWN_YES)) + return preserve; + + return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, + NM_CON_DEFAULT("sriov.preserve-on-down"), + self, + NM_SRIOV_PRESERVE_ON_DOWN_NO, + NM_SRIOV_PRESERVE_ON_DOWN_YES, + NM_SRIOV_PRESERVE_ON_DOWN_NO); +} + static NMSettingConnectionLlmnr _prop_get_connection_llmnr(NMDevice *self) { @@ -17478,7 +17498,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, } if (priv->ifindex > 0 - && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) { + && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV)) + && (!_prop_get_sriov_preserve_on_down(self, s_sriov))) { priv->sriov_reset_pending++; sriov_op_queue(self, 0, @@ -17533,7 +17554,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, nm_settings_connection_update_timestamp(sett_conn, (guint64) 0); if (priv->ifindex > 0 - && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) { + && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV)) + && (!_prop_get_sriov_preserve_on_down(self, s_sriov))) { priv->sriov_reset_pending++; sriov_op_queue(self, 0, From 6f219aa649090152388da5f893ea3f0b4f6852ab Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 5 Jun 2025 10:28:07 +0200 Subject: [PATCH 17/78] device: allow reapplying the sriov.preserve-on-down property It is useful when there is an already active device and we want to bring it down preserving the SR-IOV VFs. For example: $ nmcli connection add type ethernet ifname eni1np1 sriov.total-vfs 2 ipv4.method disabled ipv6.method disabled $ nmcli connection up ethernet-eni1np1 $ ip link show eni1np1 342: eni1np1: mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/ether 6e:cf:f0:08:74:f4 brd ff:ff:ff:ff:ff:ff vf 0 link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, ... vf 1 link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, ... $ nmcli device modify eni1np1 sriov.preserve-on-down yes $ nmcli connection down ethernet-eni1np1 $ ip link show eni1np1 342: eni1np1: mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/ether 6e:cf:f0:08:74:f4 brd ff:ff:ff:ff:ff:ff vf 0 link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, ... vf 1 link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, ... --- src/core/devices/nm-device.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 74037ca69d..2994961556 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -14050,6 +14050,13 @@ can_reapply_change(NMDevice *self, goto out_fail; } + if (nm_streq(setting_name, NM_SETTING_SRIOV_SETTING_NAME)) { + return nm_device_hash_check_invalid_keys(diffs, + NM_SETTING_SRIOV_SETTING_NAME, + error, + NM_SETTING_SRIOV_PRESERVE_ON_DOWN); + } + out_fail: g_set_error(error, NM_DEVICE_ERROR, From 3588c4868651824f68beed0eae968f8c34c48d5c Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 5 Jun 2025 10:47:19 +0200 Subject: [PATCH 18/78] NEWS: update --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 9a248c7024..f95849ad60 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,10 @@ the later release. USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! * nmcli now supports viewing and managing WireGuard peers. +* Add a new "sriov.preserve-on-down" property that controls whether + NetworkManager preserves the SR-IOV parameters set on the device + when the connection is deactivated, or whether it resets them to + their default value. ============================================= NetworkManager-1.54 From 82692cc75cca8aef2b0edeed73e653b1b4f16f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Mon, 16 Jun 2025 13:45:03 +0200 Subject: [PATCH 19/78] ipv4: remove the forwarding=ignore value It is not clear whether we can actually respect this value. For example, we should not restore the kernel's default value on deactivation or device's state change, but it is unclear if we can ensure that we'll still have the connection's configuration in all possible changes of state. Also, it is unclear if it's a desirable value that we want to support. At this point it is mostly clear that trying to configure NM managed devices externally always ends being dissapointing, no matter how hard we try. Remove this value for now, while we discuss whether it makes sense or not, so it doesn't become stable in the new 1.54 release. --- src/core/devices/nm-device.c | 2 +- src/libnm-core-impl/nm-setting-ip-config.c | 3 +-- src/libnm-core-public/nm-setting-ip-config.h | 2 -- src/libnmc-setting/settings-docs.h.in | 4 ++-- src/nmcli/gen-metadata-nm-settings-nmcli.xml.in | 4 ++-- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 2994961556..96cacca78e 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -2151,7 +2151,7 @@ nm_device_get_ipv4_forwarding(NMDevice *self) NM_CON_DEFAULT("ipv4.forwarding"), self, NM_SETTING_IP_CONFIG_FORWARDING_NO, - NM_SETTING_IP_CONFIG_FORWARDING_IGNORE, + NM_SETTING_IP_CONFIG_FORWARDING_AUTO, NM_SETTING_IP_CONFIG_FORWARDING_AUTO); } diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c index 56f9875019..ad95eb1ac5 100644 --- a/src/libnm-core-impl/nm-setting-ip-config.c +++ b/src/libnm-core-impl/nm-setting-ip-config.c @@ -7159,7 +7159,6 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass) * %NM_SETTING_IP_CONFIG_FORWARDING_YES: enabled. * %NM_SETTING_IP_CONFIG_FORWARDING_AUTO: enable if any shared connection is active, * use kernel default otherwise. - * %NM_SETTING_IP_CONFIG_FORWARDING_IGNORE: leave the forwarding unchanged. * * Since: 1.54 */ @@ -7168,7 +7167,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass) "", "", NM_SETTING_IP_CONFIG_FORWARDING_DEFAULT, - NM_SETTING_IP_CONFIG_FORWARDING_IGNORE, + NM_SETTING_IP_CONFIG_FORWARDING_AUTO, NM_SETTING_IP_CONFIG_FORWARDING_DEFAULT, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); diff --git a/src/libnm-core-public/nm-setting-ip-config.h b/src/libnm-core-public/nm-setting-ip-config.h index 63a0a9776f..625b941ff1 100644 --- a/src/libnm-core-public/nm-setting-ip-config.h +++ b/src/libnm-core-public/nm-setting-ip-config.h @@ -60,7 +60,6 @@ typedef enum { * @NM_SETTING_IP_CONFIG_FORWARDING_YES: enable forwarding * @NM_SETTING_IP_CONFIG_FORWARDING_AUTO: enable forwarding if any shared * connection is active, use kernel default otherwise - * @NM_SETTING_IP_CONFIG_FORWARDING_IGNORE: leave the forwarding unchanged * * #NMSettingIPConfigForwarding indicates whether to configure sysctl * interface-specific forwarding. When enabled, the interface will act @@ -73,7 +72,6 @@ typedef enum { NM_SETTING_IP_CONFIG_FORWARDING_NO = 0, NM_SETTING_IP_CONFIG_FORWARDING_YES = 1, NM_SETTING_IP_CONFIG_FORWARDING_AUTO = 2, - NM_SETTING_IP_CONFIG_FORWARDING_IGNORE = 3, } NMSettingIPConfigForwarding; typedef struct NMIPAddress NMIPAddress; diff --git a/src/libnmc-setting/settings-docs.h.in b/src/libnmc-setting/settings-docs.h.in index 8f7ae31d4e..c7b13d196c 100644 --- a/src/libnmc-setting/settings-docs.h.in +++ b/src/libnmc-setting/settings-docs.h.in @@ -200,7 +200,7 @@ #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_OPTIONS N_("DNS options for /etc/resolv.conf as described in resolv.conf(5) manual. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". See the resolv.conf(5) manual. Note that there is a distinction between an unset (default) list and an empty list. In nmcli, to unset the list set the value to \"\". To set an empty list, set it to \" \". Currently, an unset list has the same meaning as an empty list. That might change in the future. The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added. The valid \"ipv4.dns-options\" and \"ipv6.dns-options\" get merged together.") #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting. A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections. It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain. Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually). When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins. If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.") #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).") -#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_FORWARDING N_("Whether to configure sysctl interface-specific forwarding. When enabled, the interface will act as a router to forward the packet from one interface to another. When set to \"default\" (-1), the value from global configuration is used; if no global default is defined, \"auto\" (2) will be used. The \"forwarding\" property is ignored when \"method\" is set to \"shared\", because forwarding is always enabled in this case. The accepted values are: \"default\" (-1): use global default. \"no\" (0): disabled. \"yes\" (1): enabled. \"auto\" (2): enable if any shared connection is active, use kernel default otherwise. \"ignore\" (3): leave the forwarding unchanged.") +#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_FORWARDING N_("Whether to configure sysctl interface-specific forwarding. When enabled, the interface will act as a router to forward the packet from one interface to another. When set to \"default\" (-1), the value from global configuration is used; if no global default is defined, \"auto\" (2) will be used. The \"forwarding\" property is ignored when \"method\" is set to \"shared\", because forwarding is always enabled in this case. The accepted values are: \"default\" (-1): use global default. \"no\" (0): disabled. \"yes\" (1): enabled. \"auto\" (2): enable if any shared connection is active, use kernel default otherwise.") #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_GATEWAY N_("The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if \"never-default\" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See \"ip4-auto-default-route\".") #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_IGNORE_AUTO_DNS N_("When \"method\" is set to \"auto\" and this property to TRUE, automatically configured name servers and search domains are ignored and only name servers and search domains specified in the \"dns\" and \"dns-search\" properties, if any, are used.") #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_IGNORE_AUTO_ROUTES N_("When \"method\" is set to \"auto\" and this property to TRUE, automatically configured routes are ignored and only routes specified in the \"routes\" property, if any, are used.") @@ -236,7 +236,7 @@ #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_OPTIONS N_("DNS options for /etc/resolv.conf as described in resolv.conf(5) manual. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\" and \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"no-ip6-dotint\". See the resolv.conf(5) manual. Note that there is a distinction between an unset (default) list and an empty list. In nmcli, to unset the list set the value to \"\". To set an empty list, set it to \" \". Currently, an unset list has the same meaning as an empty list. That might change in the future. The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added. The valid \"ipv4.dns-options\" and \"ipv6.dns-options\" get merged together.") #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting. A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections. It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain. Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually). When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins. If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.") #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).") -#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_FORWARDING N_("Whether to configure sysctl interface-specific forwarding. When enabled, the interface will act as a router to forward the packet from one interface to another. When set to \"default\" (-1), the value from global configuration is used; if no global default is defined, \"auto\" (2) will be used. The \"forwarding\" property is ignored when \"method\" is set to \"shared\", because forwarding is always enabled in this case. The accepted values are: \"default\" (-1): use global default. \"no\" (0): disabled. \"yes\" (1): enabled. \"auto\" (2): enable if any shared connection is active, use kernel default otherwise. \"ignore\" (3): leave the forwarding unchanged.") +#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_FORWARDING N_("Whether to configure sysctl interface-specific forwarding. When enabled, the interface will act as a router to forward the packet from one interface to another. When set to \"default\" (-1), the value from global configuration is used; if no global default is defined, \"auto\" (2) will be used. The \"forwarding\" property is ignored when \"method\" is set to \"shared\", because forwarding is always enabled in this case. The accepted values are: \"default\" (-1): use global default. \"no\" (0): disabled. \"yes\" (1): enabled. \"auto\" (2): enable if any shared connection is active, use kernel default otherwise.") #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_GATEWAY N_("The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if \"never-default\" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See \"ip4-auto-default-route\".") #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_IGNORE_AUTO_DNS N_("When \"method\" is set to \"auto\" and this property to TRUE, automatically configured name servers and search domains are ignored and only name servers and search domains specified in the \"dns\" and \"dns-search\" properties, if any, are used.") #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_IGNORE_AUTO_ROUTES N_("When \"method\" is set to \"auto\" and this property to TRUE, automatically configured routes are ignored and only routes specified in the \"routes\" property, if any, are used.") diff --git a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in index c57366d817..a9476e38f9 100644 --- a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in +++ b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in @@ -1403,9 +1403,9 @@ format="choice (NMTernary)" values="default (-1), false/no (0), true/yes (1)" /> + values="default (-1), no (0), yes (1), auto (2)" /> From 1253cbad5afc0017ba146d8f20f059c10e63f163 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 18 Jun 2025 15:52:21 +0200 Subject: [PATCH 20/78] connectivity: fix compiler warning when building without concheck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the following: ../src/core/nm-connectivity.c:958:1: warning: ‘check_platform_config’ defined but not used [-Wunused-function] 958 | check_platform_config(NMConnectivity *self, | ^~~~~~~~~~~~~~~~~~~~~ Fixes: 91d447df19a7 ('device: don't start connectivity check on unconfigured devices') https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2224 --- src/core/nm-connectivity.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/nm-connectivity.c b/src/core/nm-connectivity.c index 22e6c0d5eb..2aa22331ea 100644 --- a/src/core/nm-connectivity.c +++ b/src/core/nm-connectivity.c @@ -25,6 +25,8 @@ #define HEADER_STATUS_ONLINE "X-NetworkManager-Status: online\r\n" +#define SD_RESOLVED_DNS ((guint64) (1LL << 0)) + /*****************************************************************************/ static NM_UTILS_LOOKUP_STR_DEFINE(_state_to_string, @@ -950,9 +952,6 @@ systemd_resolved_resolve_cb(GObject *object, GAsyncResult *res, gpointer user_da do_curl_request(cb_data, nm_str_buf_get_str(&strbuf_hosts)); } -#endif - -#define SD_RESOLVED_DNS ((guint64) (1LL << 0)) static NMConnectivityState check_platform_config(NMConnectivity *self, @@ -1013,6 +1012,7 @@ check_platform_config(NMConnectivity *self, NM_SET_OUT(reason, NULL); return NM_CONNECTIVITY_UNKNOWN; } +#endif NMConnectivityCheckHandle * nm_connectivity_check_start(NMConnectivity *self, From fcc53527159bc628628163ebedad6e991260120a Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 18 Jun 2025 09:26:52 +0200 Subject: [PATCH 21/78] Revert "sd-device: use sd_device_get_sysattr_value() to read uevent file" Revert systemd commit 17dc9ec4b6e8 ("sd-device: use sd_device_get_sysattr_value() to read uevent file"). In the NM codebase sd_device_get_sysattr_value() is currently commented out because it depends on file chase.c which is not imported. Importing that file would require another long chain of imports. Therefore, revert the commit. This reverts commit 17dc9ec4b6e82fc2a4f9809011e154f538eb8b9c. Fixes: 6a4e6fab403b ('merge: branch 'systemd' into jv/systemd-merge') --- .../src/libsystemd/sd-device/sd-device.c | 89 +++++++++++++------ 1 file changed, 63 insertions(+), 26 deletions(-) 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 13368493d7..43c601fcaa 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 @@ -805,52 +805,89 @@ static int handle_uevent_line( } int device_read_uevent_file(sd_device *device) { + _cleanup_free_ char *uevent = NULL; + const char *syspath, *key = NULL, *value = NULL, *major = NULL, *minor = NULL; + char *path; + size_t uevent_len; int r; + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + INVALID_LINE, + } state = PRE_KEY; + assert(device); if (device->uevent_loaded || device->sealed) return 0; + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + device->uevent_loaded = true; - const char *uevent; - r = sd_device_get_sysattr_value(device, "uevent", &uevent); - if (ERRNO_IS_NEG_PRIVILEGE(r) || ERRNO_IS_NEG_DEVICE_ABSENT(r)) + path = strjoina(syspath, "/uevent"); + + r = read_full_virtual_file(path, &uevent, &uevent_len); + if (r == -EACCES || ERRNO_IS_NEG_DEVICE_ABSENT(r)) /* The uevent files may be write-only, the device may be already removed, or the device * may not have the uevent file. */ return 0; if (r < 0) - return log_device_debug_errno(device, r, "sd-device: Failed to read uevent file: %m"); + return log_device_debug_errno(device, r, "sd-device: Failed to read uevent file '%s': %m", path); - _cleanup_strv_free_ char **v = NULL; - r = strv_split_newlines_full(&v, uevent, EXTRACT_RETAIN_ESCAPE); - if (r < 0) - return log_device_debug_errno(device, r, "sd-device: Failed to parse uevent file: %m"); + for (size_t i = 0; i < uevent_len; i++) + switch (state) { + case PRE_KEY: + if (!strchr(NEWLINE, uevent[i])) { + key = &uevent[i]; - const char *major = NULL, *minor = NULL; - STRV_FOREACH(s, v) { - char *eq = strchr(*s, '='); - if (!eq) { - log_device_debug(device, "sd-device: Invalid uevent line, ignoring: %s", *s); - continue; + state = KEY; + } + + break; + case KEY: + if (uevent[i] == '=') { + uevent[i] = '\0'; + + state = PRE_VALUE; + } else if (strchr(NEWLINE, uevent[i])) { + uevent[i] = '\0'; + log_device_debug(device, "sd-device: Invalid uevent line '%s', ignoring", key); + + state = PRE_KEY; + } + + break; + case PRE_VALUE: + value = &uevent[i]; + state = VALUE; + + _fallthrough_; /* to handle empty property */ + case VALUE: + if (strchr(NEWLINE, uevent[i])) { + uevent[i] = '\0'; + + r = handle_uevent_line(device, key, value, &major, &minor); + if (r < 0) + log_device_debug_errno(device, r, "sd-device: Failed to handle uevent entry '%s=%s', ignoring: %m", key, value); + + state = PRE_KEY; + } + + break; + default: + assert_not_reached(); } - *eq = '\0'; - - r = handle_uevent_line(device, *s, eq + 1, &major, &minor); - if (r < 0) - log_device_debug_errno(device, r, - "sd-device: Failed to handle uevent entry '%s=%s', ignoring: %m", - *s, eq + 1); - } - if (major) { r = device_set_devnum(device, major, minor); if (r < 0) - log_device_debug_errno(device, r, - "sd-device: Failed to set 'MAJOR=%s' and/or 'MINOR=%s' from uevent, ignoring: %m", - major, strna(minor)); + log_device_debug_errno(device, r, "sd-device: Failed to set 'MAJOR=%s' or 'MINOR=%s' from '%s', ignoring: %m", major, strna(minor), path); } if (device_in_subsystem(device, "drivers")) { From 77c99b61c09725c418d0abf2be26bb9bc24d1a19 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 18 Jun 2025 09:31:06 +0200 Subject: [PATCH 22/78] Revert "sd-device: use sd_device_get_sysattr_value() to read special symlinks" Revert systemd commit 6ebbdcc0ddba ("sd-device: use sd_device_get_sysattr_value() to read special symlinks"). In the NM codebase sd_device_get_sysattr_value() is currently commented out because it depends on file chase.c which is not imported. Importing that file would require another long chain of imports. Therefore, revert the commit. This reverts commit 6ebbdcc0ddbacce732001823cf2be2a1d4381c60. Fixes: 6a4e6fab403b ('merge: branch 'systemd' into jv/systemd-merge') --- .../src/libsystemd/sd-device/sd-device.c | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) 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 43c601fcaa..fce184df0d 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 @@ -1263,24 +1263,35 @@ _public_ int sd_device_get_subsystem(sd_device *device, const char **ret) { return r; if (!device->subsystem_set) { - const char *subsystem; + _cleanup_free_ char *subsystem = NULL; + const char *syspath; + char *path; - r = sd_device_get_sysattr_value(device, "subsystem", &subsystem); + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + /* read 'subsystem' link */ + path = strjoina(syspath, "/subsystem"); + r = readlink_value(path, &subsystem); if (r < 0 && r != -ENOENT) return log_device_debug_errno(device, r, "sd-device: Failed to read subsystem for %s: %m", device->devpath); - if (r >= 0) + + if (subsystem) r = device_set_subsystem(device, subsystem); /* use implicit names */ else if (!isempty(path_startswith(device->devpath, "/module/"))) r = device_set_subsystem(device, "module"); - else if (strstr(device->devpath, "/drivers/") || endswith(device->devpath, "/drivers")) + else if (strstr(syspath, "/drivers/") || endswith(syspath, "/drivers")) r = device_set_drivers_subsystem(device); else if (!isempty(PATH_STARTSWITH_SET(device->devpath, "/class/", "/bus/"))) r = device_set_subsystem(device, "subsystem"); - else - r = device_set_subsystem(device, NULL); + else { + device->subsystem_set = true; + r = 0; + } if (r < 0) return log_device_debug_errno(device, r, "sd-device: Failed to set subsystem for %s: %m", @@ -1392,8 +1403,6 @@ int device_set_driver(sd_device *device, const char *driver) { #if 0 /* NM_IGNORED */ _public_ int sd_device_get_driver(sd_device *device, const char **ret) { - int r; - assert_return(device, -EINVAL); r = device_read_uevent_file(device); @@ -1401,12 +1410,20 @@ _public_ int sd_device_get_driver(sd_device *device, const char **ret) { return r; if (!device->driver_set) { - const char *driver = NULL; + _cleanup_free_ char *driver = NULL; + const char *syspath; + char *path; + int r; - r = sd_device_get_sysattr_value(device, "driver", &driver); + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = strjoina(syspath, "/driver"); + r = readlink_value(path, &driver); if (r < 0 && r != -ENOENT) return log_device_debug_errno(device, r, - "sd-device: Failed to read driver: %m"); + "sd-device: readlink(\"%s\") failed: %m", path); r = device_set_driver(device, driver); if (r < 0) From 9f510533f11fface4db1bc2ea27a9810c970ed8c Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 18 Jun 2025 10:37:49 +0200 Subject: [PATCH 23/78] systemd: ignore device_is_devtype() The function uses symbol sd_device_get_devtype() which is currently commented out. Therefore, NM dynamically links to the symbol from the libsystemd shared object, which breaks on machines without systemd. Before: $ nm -u build/src/core/NetworkManager | grep sd_device U sd_device_get_sysattr_value@LIBSYSTEMD_240 $ After: $ nm -u build/src/core/NetworkManager | grep sd_device $ Fixes: 6a4e6fab403b ('merge: branch 'systemd' into jv/systemd-merge') --- src/libnm-systemd-core/src/libsystemd/sd-device/device-util.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.c b/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.c index 25290e630a..e289523f72 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.c +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.c @@ -133,7 +133,6 @@ char** device_make_log_fields(sd_device *device) { return TAKE_PTR(strv); } - #endif /* NM_IGNORED */ bool device_in_subsystem(sd_device *device, const char *subsystem) { @@ -145,6 +144,7 @@ bool device_in_subsystem(sd_device *device, const char *subsystem) { return streq_ptr(s, subsystem); } +#if 0 /* NM_IGNORED */ bool device_is_devtype(sd_device *device, const char *devtype) { const char *s = NULL; @@ -153,3 +153,4 @@ bool device_is_devtype(sd_device *device, const char *devtype) { (void) sd_device_get_devtype(device, &s); return streq_ptr(s, devtype); } +#endif /* NM_IGNORED */ From e26e965134901bcd03241981f737bc491d3f4e91 Mon Sep 17 00:00:00 2001 From: Filip Pokryvka Date: Fri, 20 Jun 2025 09:19:23 +0200 Subject: [PATCH 24/78] release: bump version to 1.55.1 (development) --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 1e28a72650..6125afa53d 100644 --- a/meson.build +++ b/meson.build @@ -5,7 +5,7 @@ project( # NOTE: When incrementing version also add corresponding # NM_VERSION_x_y_z macros in # "src/libnm-core-public/nm-version-macros.h.in" - version: '1.55.0', + version: '1.55.1', license: 'GPL2+', default_options: [ 'buildtype=debugoptimized', From 6a13e8d3690f2d25059709d5198e38750fb5b294 Mon Sep 17 00:00:00 2001 From: Wen Liang Date: Sun, 22 Jun 2025 16:03:57 -0400 Subject: [PATCH 25/78] core: expose the version info capability of IPv4 forwarding support This commit adds NM_VERSION_INFO_CAPABILITY_IPV4_FORWARDING to the VersionInfo D-Bus property, allowing clients such as nmstate to check the NetworkManager's support of configuring per-device IPv4 sysctl forwarding setting directly via the capabilities bitmask instead of relying on the NetworkManager version comparisons. --- src/core/nm-manager.c | 3 ++- src/libnm-core-public/nm-dbus-interface.h | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c index 1012b532df..03e0c1fa24 100644 --- a/src/core/nm-manager.c +++ b/src/core/nm-manager.c @@ -478,7 +478,8 @@ _version_info_get(void) * Each of the array's elements has 32 bits. This means that capabilities * with index 0-31 goes to element #1, with index 32-63 to element #2, * with index 64-95 to element #3 and so on. */ - 1 << NM_VERSION_INFO_CAPABILITY_SYNC_ROUTE_WITH_TABLE, + (1 << NM_VERSION_INFO_CAPABILITY_SYNC_ROUTE_WITH_TABLE) + | (1 << NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING), }; return nm_g_variant_new_au(arr, G_N_ELEMENTS(arr)); diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index a0e30f9145..2d14638ab2 100644 --- a/src/libnm-core-public/nm-dbus-interface.h +++ b/src/libnm-core-public/nm-dbus-interface.h @@ -100,6 +100,9 @@ * https://issues.redhat.com/browse/RHEL-66262 * https://issues.redhat.com/browse/RHEL-67324 * + * @NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING: Indicates that NetworkManager supports + * configuring per-device IPv4 sysctl forwarding setting. Since: 1.56. + * * The numeric values represent the bit index of the capability. These capabilities * can be queried in the "VersionInfo" D-Bus property. * @@ -107,6 +110,7 @@ */ typedef enum { NM_VERSION_INFO_CAPABILITY_SYNC_ROUTE_WITH_TABLE = 0, + NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING = 1, } NMVersionInfoCapability; /** From 2ffaebd4ae93c2dd9549c360d4c358d611ef3257 Mon Sep 17 00:00:00 2001 From: Mary Strodl Date: Thu, 30 Jan 2025 15:54:18 -0500 Subject: [PATCH 26/78] platform: support the RT_VIA attribute for IPv4 routes The RT_VIA attribute is used to specify a gateway of a different address family. It is currently used only for IPv4 routes. [bgalvani@redhat.com: amended the commit message] --- src/libnm-platform/nm-linux-platform.c | 17 ++++++++++++++++- src/libnm-platform/nm-platform.h | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index 64209b5321..45770de534 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -5828,7 +5828,22 @@ _nl_msg_new_route(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPObject *ob /* We currently don't have need for multi-hop routes... */ if (IS_IPv4) { - NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip4_route.gateway); + if (!obj->ip4_route.gateway && obj->ip4_route.via.addr_family) { + struct rtvia *rtvia; + + rtvia = nla_data(nla_reserve( + msg, + RTA_VIA, + sizeof(*rtvia) + nm_utils_addr_family_to_size(obj->ip4_route.via.addr_family))); + if (!rtvia) + goto nla_put_failure; + rtvia->rtvia_family = obj->ip4_route.via.addr_family; + memcpy(rtvia->rtvia_addr, + obj->ip4_route.via.addr.addr_ptr, + nm_utils_addr_family_to_size(obj->ip4_route.via.addr_family)); + } else { + NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip4_route.gateway); + } } else { if (!IN6_IS_ADDR_UNSPECIFIED(&obj->ip6_route.gateway)) NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip6_route.gateway); diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index e4ccb9b1cb..cd6c26e326 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -443,6 +443,12 @@ struct _NMPlatformIP4Route { * the first hop. */ in_addr_t gateway; + /* RTA_VIA. Part of the primary key for a route. Allows a gateway for a + * route to exist in a different address family. + * Only valid if: n_nexthops == 1, gateway == 0, via.family != 0 + */ + NMIPAddrTyped via; + /* RTA_PREFSRC (called "src" by iproute2). * * pref_src is part of the ID of an IPv4 route. When deleting a route, @@ -2404,6 +2410,14 @@ nm_platform_ip_route_get_gateway(int addr_family, const NMPlatformIPRoute *route return &((NMPlatformIP6Route *) route)->gateway; } +static inline const NMIPAddrTyped * +nm_platform_ip4_route_get_via(const NMPlatformIP4Route *route) +{ + nm_assert(route); + + return &route->via; +} + static inline gconstpointer nm_platform_ip_route_get_pref_src(int addr_family, const NMPlatformIPRoute *route) { From 9c70a437757e1c0ee0c070074c0871da7096b5fa Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 16 Jun 2025 13:52:47 +0200 Subject: [PATCH 27/78] platform: use the "via" attribute in route NMPObject methods Update the cmd_obj_hash_update(), cmd_obj_cmp(), cmd_obj_to_string() NMPObject methods for IPv4 routes to consider the "via" attribute. --- src/libnm-platform/nm-linux-platform.c | 4 +++- src/libnm-platform/nm-platform.c | 22 ++++++++++++++++++---- src/libnm-platform/nm-platform.h | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index 45770de534..50322a7a35 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -5828,9 +5828,11 @@ _nl_msg_new_route(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPObject *ob /* We currently don't have need for multi-hop routes... */ if (IS_IPv4) { - if (!obj->ip4_route.gateway && obj->ip4_route.via.addr_family) { + if (obj->ip4_route.gateway == INADDR_ANY && obj->ip4_route.via.addr_family != AF_UNSPEC) { struct rtvia *rtvia; + nm_assert(obj->ip4_route.via.addr_family == AF_INET6); + rtvia = nla_data(nla_reserve( msg, RTA_VIA, diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index fd966d2ce7..559c7dc3a4 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -7199,7 +7199,7 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route, { char *buf0; char s_network[INET_ADDRSTRLEN]; - char s_gateway[INET_ADDRSTRLEN]; + char s_gateway[INET6_ADDRSTRLEN]; char s_pref_src[INET_ADDRSTRLEN]; char str_dev[30]; char str_mss[32]; @@ -7228,10 +7228,13 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route, inet_ntop(AF_INET, &route->network, s_network, sizeof(s_network)); - if (route->gateway == 0) - s_gateway[0] = '\0'; - else + if (route->gateway != INADDR_ANY) { inet_ntop(AF_INET, &route->gateway, s_gateway, sizeof(s_gateway)); + } else if (route->via.addr_family == AF_INET6) { + inet_ntop(AF_INET6, route->via.addr.addr_ptr, s_gateway, sizeof(s_gateway)); + } else { + s_gateway[0] = '\0'; + } nm_strbuf_append( &buf, @@ -8967,6 +8970,9 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj, nm_hash_update_vals(h, obj->ifindex, n_nexthops, + obj->via.addr_family, + obj->via.addr_family == AF_INET6 ? obj->via.addr.addr6 + : in6addr_any, obj->gateway, _ip4_route_weight_normalize(n_nexthops, obj->weight, FALSE)); } @@ -9117,6 +9123,10 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a, if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) { NM_CMP_FIELD(a, b, ifindex); NM_CMP_FIELD(a, b, gateway); + NM_CMP_FIELD(a, b, via.addr_family); + if (a->via.addr_family == AF_INET6) { + NM_CMP_FIELD_IN6ADDR(a, b, via.addr.addr6); + } n_nexthops = nm_platform_ip4_route_get_n_nexthops(a); NM_CMP_DIRECT(n_nexthops, nm_platform_ip4_route_get_n_nexthops(b)); NM_CMP_DIRECT(_ip4_route_weight_normalize(n_nexthops, a->weight, FALSE), @@ -9142,6 +9152,10 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a, NM_CMP_FIELD_UNSAFE(a, b, metric_any); NM_CMP_FIELD(a, b, metric); NM_CMP_FIELD(a, b, gateway); + NM_CMP_FIELD(a, b, via.addr_family); + if (a->via.addr_family == AF_INET6) { + NM_CMP_FIELD_IN6ADDR(a, b, via.addr.addr6); + } if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) { n_nexthops = nm_platform_ip4_route_get_n_nexthops(a); NM_CMP_DIRECT(n_nexthops, nm_platform_ip4_route_get_n_nexthops(b)); diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index cd6c26e326..18204e39bc 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -445,7 +445,7 @@ struct _NMPlatformIP4Route { /* RTA_VIA. Part of the primary key for a route. Allows a gateway for a * route to exist in a different address family. - * Only valid if: n_nexthops == 1, gateway == 0, via.family != 0 + * Only valid if: n_nexthops == 1, gateway == 0, via.family != AF_UNSPEC */ NMIPAddrTyped via; From 00257a9cf7afbe9bf3a6c003788ccadded0f5143 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 16 Jun 2025 13:50:39 +0200 Subject: [PATCH 28/78] platform: parse the RT_VIA route attribute Parse the "via" attribute in netlink routes received by kernel, so that we can update the internal cache. --- src/libnm-platform/nm-linux-platform.c | 36 +++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index 50322a7a35..2cc50f8f77 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -4013,6 +4013,7 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter [RTA_PREF] = {.type = NLA_U8}, [RTA_FLOW] = {.type = NLA_U32}, [RTA_CACHEINFO] = {.minlen = nm_offsetofend(struct rta_cacheinfo, rta_tsage)}, + [RTA_VIA] = {.minlen = nm_offsetofend(struct rtvia, rtvia_family)}, [RTA_METRICS] = {.type = NLA_NESTED}, [RTA_MULTIPATH] = {.type = NLA_NESTED}, }; @@ -4029,9 +4030,11 @@ _new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter guint8 weight; int ifindex; NMIPAddr gateway; + gboolean is_via; } nh = { .found = FALSE, .has_more = FALSE, + .is_via = FALSE, }; guint v4_n_nexthops = 0; NMPlatformIP4RtNextHop v4_nh_extra_nexthops_stack[10]; @@ -4200,13 +4203,26 @@ rta_multipath_done: return nm_assert_unreachable_val(NULL); } - if (tb[RTA_OIF] || tb[RTA_GATEWAY] || tb[RTA_FLOW]) { + if (tb[RTA_OIF] || tb[RTA_GATEWAY] || tb[RTA_FLOW] || tb[RTA_VIA]) { int ifindex = 0; NMIPAddr gateway = {}; if (tb[RTA_OIF]) ifindex = nla_get_u32(tb[RTA_OIF]); - if (_check_addr_or_return_null(tb, RTA_GATEWAY, addr_len)) + + if (tb[RTA_VIA]) { + struct rtvia *rtvia; + + /* Used when the gateway family doesn't match the route family */ + rtvia = nla_data(tb[RTA_VIA]); + if (rtvia->rtvia_family != AF_INET6) + return NULL; + if (nla_len(tb[RTA_VIA]) < sizeof(struct rtvia) + sizeof(struct in6_addr)) + return NULL; + + nh.is_via = TRUE; + memcpy(&gateway, rtvia->rtvia_addr, sizeof(struct in6_addr)); + } else if (_check_addr_or_return_null(tb, RTA_GATEWAY, addr_len)) memcpy(&gateway, nla_data(tb[RTA_GATEWAY]), addr_len); if (!nh.found) { @@ -4222,6 +4238,9 @@ rta_multipath_done: } else { /* Kernel supports new style nexthop configuration, * verify that it is a duplicate and ignore old-style nexthop. */ + if (nh.is_via) + return NULL; + if (nh.ifindex != ifindex || memcmp(&nh.gateway, &gateway, addr_len) != 0) { /* we have a RTA_MULTIPATH attribute that does not agree. * That seems not right. Error out. */ @@ -4237,7 +4256,7 @@ rta_multipath_done: * 1 (lo). Of course it does!! */ if (nh.found) { if (IS_IPv4) { - if (nh.ifindex != 0 || nh.gateway.addr4 != 0) { + if (nh.ifindex != 0 || nh.gateway.addr4 != 0 || nh.is_via) { /* we only accept kernel to notify about the ifindex/gateway, if it * is zero. This is only to be a bit forgiving, but we really don't * know how to handle such routes that have an ifindex. */ @@ -4336,9 +4355,14 @@ rta_multipath_done: if (tb[RTA_PRIORITY]) obj->ip_route.metric = nla_get_u32(tb[RTA_PRIORITY]); - if (IS_IPv4) - obj->ip4_route.gateway = nh.gateway.addr4; - else + if (IS_IPv4) { + if (nh.is_via) { + obj->ip4_route.via.addr_family = AF_INET6; + obj->ip4_route.via.addr = nh.gateway; + } else { + obj->ip4_route.gateway = nh.gateway.addr4; + } + } else obj->ip6_route.gateway = nh.gateway.addr6; if (IS_IPv4) From 24ab3308fea8400d79b72d406e33a5e40bf71455 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 16 Jun 2025 13:49:37 +0200 Subject: [PATCH 29/78] platform: add test for route via attribute --- src/core/platform/tests/test-route.c | 74 ++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c index f8f7070bff..fbad2447a9 100644 --- a/src/core/platform/tests/test-route.c +++ b/src/core/platform/tests/test-route.c @@ -623,6 +623,79 @@ test_ip4_zero_gateway(void) nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); } +static void +test_via(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + GPtrArray *routes; + NMPlatformIP4Route rts[1]; + struct in6_addr gateway6; + const int metric = 22987; + NMPlatformIP4Route route4; + guint mss = 1000; + in_addr_t net4; + + /* Test IPv4 routes with a IPv6 gateway (using RTA_VIA attribute) */ + + inet_pton(AF_INET6, "fd01::1", &gateway6); + inet_pton(AF_INET, "1.2.3.4", &net4); + + /* Add direct route to IPv6 gateway: ip route add dev $DEV fd01::1/128 */ + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + gateway6, + 128, + in6addr_any, + in6addr_any, + metric, + mss); + g_assert(nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &gateway6, 128, metric, NULL, 0)); + + /* Add IPv4 route via IPv6 gateway: ip route add dev $DEV 1.2.3.4/32 via inet6 fd01::1 */ + route4 = (NMPlatformIP4Route) { + .ifindex = ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .network = net4, + .plen = 32, + .metric = metric, + .via.addr_family = AF_INET6, + .via.addr.addr6 = gateway6, + .mss = mss, + }; + g_assert(NMTST_NM_ERR_SUCCESS( + nm_platform_ip4_route_add(NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &route4, NULL))); + g_assert(nmtstp_ip4_route_get(NM_PLATFORM_GET, ifindex, net4, 32, metric, 0)); + + /* Test route listing */ + routes = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, ifindex); + g_assert_cmpint(routes->len, ==, 1); + + memset(rts, 0, sizeof(rts)); + rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[0].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_LINK); + rts[0].network = net4; + rts[0].plen = 32; + rts[0].ifindex = ifindex; + rts[0].gateway = INADDR_ANY; + rts[0].metric = metric; + rts[0].mss = mss; + rts[0].via.addr_family = AF_INET6; + rts[0].via.addr.addr6 = gateway6; + rts[0].n_nexthops = 1; + nmtst_platform_ip4_routes_equal_aptr((const NMPObject *const *) routes->pdata, + rts, + routes->len, + TRUE); + g_ptr_array_unref(routes); + + /* Delete routes */ + g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, ifindex, gateway6, 128, metric)); + g_assert(!nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &gateway6, 128, metric, NULL, 0)); + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, net4, 32, metric)); + g_assert(!nmtstp_ip4_route_get(NM_PLATFORM_GET, ifindex, net4, 32, metric, 0)); +} + static void test_ip4_route_options(gconstpointer test_data) { @@ -2421,6 +2494,7 @@ _nmtstp_setup_tests(void) add_test_func("/route/ip4_route_get", test_ip4_route_get); add_test_func("/route/ip6_route_get", test_ip6_route_get); add_test_func("/route/ip4_zero_gateway", test_ip4_zero_gateway); + add_test_func("/route/via", test_via); } if (nmtstp_is_root_test()) { From 7bb898fa125c6c1e835e9adbdca4d96138c126c2 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 26 Jun 2025 12:02:35 +0200 Subject: [PATCH 30/78] libnm: fix g-ir-scanner build warning Fix the following build warning emitted by g-ir-scanner: ../src/libnm-core-public/nm-dbus-interface.h:103: Warning: NM: "@NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING" parameter unexpected at this location: * @NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING: Indicates that NetworkManager supports ^ Fixes: 6a13e8d3690f ('core: expose the version info capability of IPv4 forwarding support') --- src/libnm-core-public/nm-dbus-interface.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index 2d14638ab2..38802bc7ad 100644 --- a/src/libnm-core-public/nm-dbus-interface.h +++ b/src/libnm-core-public/nm-dbus-interface.h @@ -99,7 +99,6 @@ * on connection down. * https://issues.redhat.com/browse/RHEL-66262 * https://issues.redhat.com/browse/RHEL-67324 - * * @NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING: Indicates that NetworkManager supports * configuring per-device IPv4 sysctl forwarding setting. Since: 1.56. * From 5ab04c8f567ca7e1d7b494c1ee13a5b9c907f76c Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 24 Jun 2025 09:54:27 +0200 Subject: [PATCH 31/78] libnm: change "sriov.preserve-on-down" symbols version to 1.54 The "sriov.preserve-on-down" libnm symbols were backported to the 1.54 branch before the final release. Change their version on main to be "1.54". https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2229 --- NEWS | 8 ++++---- src/libnm-client-impl/libnm.ver | 6 +----- src/libnm-core-impl/nm-setting-sriov.c | 4 ++-- src/libnm-core-public/nm-setting-sriov.h | 4 ++-- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index f95849ad60..852a0f380a 100644 --- a/NEWS +++ b/NEWS @@ -9,10 +9,6 @@ the later release. USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! * nmcli now supports viewing and managing WireGuard peers. -* Add a new "sriov.preserve-on-down" property that controls whether - NetworkManager preserves the SR-IOV parameters set on the device - when the connection is deactivated, or whether it resets them to - their default value. ============================================= NetworkManager-1.54 @@ -28,6 +24,10 @@ Overview of changes since NetworkManager-1.52 * Add support for configuring the loopback interface in nmtui. * Most of the properties of ovs-bridge and ovs-port connections can now be reapplied at runtime without bringing the connection down. +* Add a new "sriov.preserve-on-down" property that controls whether + NetworkManager preserves the SR-IOV parameters set on the device + when the connection is deactivated, or whether it resets them to + their default value. ============================================= NetworkManager-1.52 diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver index e7ca51da88..ad05a4e29c 100644 --- a/src/libnm-client-impl/libnm.ver +++ b/src/libnm-client-impl/libnm.ver @@ -2073,10 +2073,6 @@ global: nm_setting_prefix_delegation_get_subnet_id; nm_setting_prefix_delegation_get_type; nm_setting_prefix_delegation_new; -} libnm_1_52_0; - -libnm_1_56_0 { -global: nm_setting_sriov_get_preserve_on_down; nm_sriov_preserve_on_down_get_type; -} libnm_1_54_0; +} libnm_1_52_0; diff --git a/src/libnm-core-impl/nm-setting-sriov.c b/src/libnm-core-impl/nm-setting-sriov.c index aec9c4dd54..d6be0ec1d3 100644 --- a/src/libnm-core-impl/nm-setting-sriov.c +++ b/src/libnm-core-impl/nm-setting-sriov.c @@ -834,7 +834,7 @@ nm_setting_sriov_clear_vfs(NMSettingSriov *setting) * * Returns: the value contained in the #NMSettingSriov:preserve-on-down property. * - * Since: 1.56 + * Since: 1.54 */ NMSriovPreserveOnDown nm_setting_sriov_get_preserve_on_down(NMSettingSriov *setting) @@ -1496,7 +1496,7 @@ nm_setting_sriov_class_init(NMSettingSriovClass *klass) * looks up a global default value in the configuration; in case no such value is * defined, it uses %NM_SRIOV_PRESERVE_ON_DOWN_NO as fallback. * - * Since: 1.56 + * Since: 1.54 */ _nm_setting_property_define_direct_enum(properties_override, obj_properties, diff --git a/src/libnm-core-public/nm-setting-sriov.h b/src/libnm-core-public/nm-setting-sriov.h index e45620fe8a..60c5005c0b 100644 --- a/src/libnm-core-public/nm-setting-sriov.h +++ b/src/libnm-core-public/nm-setting-sriov.h @@ -65,7 +65,7 @@ typedef enum { * @NM_SRIOV_PRESERVE_ON_DOWN_YES: preserve the SR-IOV parameters set on * the device when the connection is deactivated * - * Since: 1.56 + * Since: 1.54 */ typedef enum { NM_SRIOV_PRESERVE_ON_DOWN_DEFAULT = -1, @@ -140,7 +140,7 @@ NM_AVAILABLE_IN_1_14 gboolean nm_setting_sriov_remove_vf_by_index(NMSettingSriov *setting, guint index); NM_AVAILABLE_IN_1_14 void nm_setting_sriov_clear_vfs(NMSettingSriov *setting); -NM_AVAILABLE_IN_1_56 +NM_AVAILABLE_IN_1_54 NMSriovPreserveOnDown nm_setting_sriov_get_preserve_on_down(NMSettingSriov *setting); NM_AVAILABLE_IN_1_14 NMTernary nm_setting_sriov_get_autoprobe_drivers(NMSettingSriov *setting); From ea6af6d8066673e39e287c19aa3cbde47c707f47 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 30 Jun 2025 22:33:29 +0200 Subject: [PATCH 32/78] libnm-core: change version of ip4-forwarding capability NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING was backported to branch nm-1-54 before the 1.54 release. Update the symbol version. --- src/libnm-core-public/nm-dbus-interface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index 38802bc7ad..27facc4780 100644 --- a/src/libnm-core-public/nm-dbus-interface.h +++ b/src/libnm-core-public/nm-dbus-interface.h @@ -100,7 +100,7 @@ * https://issues.redhat.com/browse/RHEL-66262 * https://issues.redhat.com/browse/RHEL-67324 * @NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING: Indicates that NetworkManager supports - * configuring per-device IPv4 sysctl forwarding setting. Since: 1.56. + * configuring per-device IPv4 sysctl forwarding setting. Since: 1.54. * * The numeric values represent the bit index of the capability. These capabilities * can be queried in the "VersionInfo" D-Bus property. From 8e40f7e289c94f578f04bb8661d70eca503bcee6 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 30 Jun 2025 22:38:18 +0200 Subject: [PATCH 33/78] manager: add a capability for "sriov.preserve-on-down" Add a new capability to indicate that NetworkManager supports the "sriov.preserve-on-down" connection property. With this, clients can set the property only when supported, without the risk of creating an invalid connection. --- src/core/nm-manager.c | 3 ++- src/libnm-core-public/nm-dbus-interface.h | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c index 03e0c1fa24..fce3707125 100644 --- a/src/core/nm-manager.c +++ b/src/core/nm-manager.c @@ -479,7 +479,8 @@ _version_info_get(void) * with index 0-31 goes to element #1, with index 32-63 to element #2, * with index 64-95 to element #3 and so on. */ (1 << NM_VERSION_INFO_CAPABILITY_SYNC_ROUTE_WITH_TABLE) - | (1 << NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING), + | (1 << NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING) + | (1 << NM_VERSION_INFO_CAPABILITY_SRIOV_PRESERVE_ON_DOWN), }; return nm_g_variant_new_au(arr, G_N_ELEMENTS(arr)); diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index 27facc4780..78241193a4 100644 --- a/src/libnm-core-public/nm-dbus-interface.h +++ b/src/libnm-core-public/nm-dbus-interface.h @@ -101,6 +101,8 @@ * https://issues.redhat.com/browse/RHEL-67324 * @NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING: Indicates that NetworkManager supports * configuring per-device IPv4 sysctl forwarding setting. Since: 1.54. + * @NM_VERSION_INFO_CAPABILITY_SRIOV_PRESERVE_ON_DOWN: NetworkManager supports the + * "sriov.preserve-on-down" property. Since: 1.54 * * The numeric values represent the bit index of the capability. These capabilities * can be queried in the "VersionInfo" D-Bus property. @@ -108,8 +110,9 @@ * Since: 1.42 */ typedef enum { - NM_VERSION_INFO_CAPABILITY_SYNC_ROUTE_WITH_TABLE = 0, - NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING = 1, + NM_VERSION_INFO_CAPABILITY_SYNC_ROUTE_WITH_TABLE = 0, + NM_VERSION_INFO_CAPABILITY_IP4_FORWARDING = 1, + NM_VERSION_INFO_CAPABILITY_SRIOV_PRESERVE_ON_DOWN = 2, } NMVersionInfoCapability; /** From 165e5df6e070b9315c8062b94b0133e2e1534678 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 23 Jun 2025 10:49:21 +0200 Subject: [PATCH 34/78] nmcli: accept certain IP settings on port connections Commit bb850fda0ed9 ('nmcli: connection: process port-type, type and controller first') started correctly rejecting IP configuration on port connections. However, previously nmcli would accept IP parameters for ports when using a specific parameters order. To avoid breaking user scripts that may have relied on this behavior, introduce a backward compatibility quirk. Specifically, nmcli accepts a disabled/ignore IP method on a port connection. For any other IP setting on a port connection, a specific error message is now shown. https://issues.redhat.com/browse/RHEL-90756 https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2227 --- src/nmcli/connections.c | 99 +++++++++++++--- .../test_004.expected | 40 +++++++ src/tests/client/test-client.py | 107 ++++++++++++++++++ tools/test-networkmanager-service.py | 1 + 4 files changed, 234 insertions(+), 13 deletions(-) diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c index f53f2610d0..409f80673f 100644 --- a/src/nmcli/connections.c +++ b/src/nmcli/connections.c @@ -5266,6 +5266,45 @@ _copy_connection_properties(const char ***dst, return count; } +static gboolean +is_ip_setting_for_port_connection(NMConnection *connection, + const char *setting_name, + const char *property_name, + const char *value, + gboolean *out_is_method_disabled) +{ + NMSettingConnection *s_con; + + *out_is_method_disabled = FALSE; + + if (!NM_IN_STRSET(setting_name, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_SETTING_NAME)) + return FALSE; + + s_con = nm_connection_get_setting_connection(connection); + if (!s_con) + return FALSE; + + if (!nm_setting_connection_get_controller(s_con)) + return FALSE; + + if (nm_streq(property_name, NM_SETTING_IP_CONFIG_METHOD)) { + if (nm_streq(setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME)) { + if (nm_streq(value, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) + *out_is_method_disabled = TRUE; + } else { + /* IPv6 */ + if (NM_IN_STRSET(value, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) + *out_is_method_disabled = TRUE; + } + } + + return TRUE; +} + gboolean nmc_process_connection_properties(NmCli *nmc, NMConnection *connection, @@ -5428,24 +5467,58 @@ nmc_process_connection_properties(NmCli *nmc, if (argc == 1 && nmc->complete) complete_property_name(nmc, connection, modifier, option_sett, option_prop); - option_sett_expanded = - check_valid_name(option_sett, type_settings, port_settings, &local); - if (!option_sett_expanded) { - g_set_error(error, - NMCLI_ERROR, - NMC_RESULT_ERROR_USER_INPUT, - _("Error: invalid or not allowed setting '%s': %s."), - option_sett, - local->message); - g_clear_error(&local); - return FALSE; - } - argc--; argv++; if (!get_value(&value, &argc, &argv, option_orig, error)) return FALSE; + option_sett_expanded = + check_valid_name(option_sett, type_settings, port_settings, &local); + if (!option_sett_expanded) { + gboolean raise_error = TRUE; + gboolean is_method_disabled = FALSE; + + /* The setting does not exist or is now allowed for the given + * connection type or for the given port type. In the past nmcli + * accepted IP-config properties for port connections under some + * circumstances. For backward bug compatibility, still allow + * the user to set the IP method to disabled/ignore for ports, + * so that we don't break user scripts. + * */ + if (is_ip_setting_for_port_connection(connection, + option_sett, + option_prop, + value, + &is_method_disabled)) { + if (is_method_disabled) { + /* Allowed */ + option_sett_expanded = option_sett; + raise_error = FALSE; + g_clear_error(&local); + } else { + /* The property is not a disabled/ignore IP method. Raise a + * meaningful error, instead of the generic "setting X is not + * among LIST" */ + g_clear_error(&local); + g_set_error(&local, + NMCLI_ERROR, + NMC_RESULT_ERROR_USER_INPUT, + _("port connections cannot have IP configuration")); + raise_error = TRUE; + } + } + if (raise_error) { + g_set_error(error, + NMCLI_ERROR, + NMC_RESULT_ERROR_USER_INPUT, + _("Error: invalid or not allowed setting '%s': %s."), + option_sett, + local->message); + g_clear_error(&local); + return FALSE; + } + } + if (!argc && nmc->complete) { complete_property(nmc, option_sett, option_prop, value ?: "", connection); break; diff --git a/src/tests/client/test-client.check-on-disk/test_004.expected b/src/tests/client/test-client.check-on-disk/test_004.expected index 7c81ae3b85..09422f1088 100644 --- a/src/tests/client/test-client.check-on-disk/test_004.expected +++ b/src/tests/client/test-client.check-on-disk/test_004.expected @@ -57981,3 +57981,43 @@ con-xx2 UUID-con-xx2-REPLACED-REPLACED-REPLA ethernet 0 nigdy con-1 5fcfd6d7-1e63-3332-8826-a7eda103792d ethernet 0 nigdy tak 0 nie /org/freedesktop/NetworkManager/Settings/Connection/1 nie -- -- -- -- /etc/NetworkManager/system-connections/con-1 <<< +size: 359 +location: src/tests/client/test-client.py:test_004()/934 +cmd: $NMCLI connection add type ethernet ifname foobar con-name con-port1 ipv4.method disabled ipv6.method ignore connection.port-type bridge connection.controller bridge1 +lang: C +returncode: 0 +stdout: 82 bytes +>>> +Connection 'con-port1' (UUID-con-port1-REPLACED-REPLACED-REP) successfully added. + +<<< +size: 353 +location: src/tests/client/test-client.py:test_004()/935 +cmd: $NMCLI connection add type ethernet ifname foobar con-name ethernet-foobar ipv6.method auto connection.port-type bridge connection.controller bridge1 +lang: C +returncode: 2 +stderr: 93 bytes +>>> +Error: invalid or not allowed setting 'ipv6': port connections cannot have IP configuration. + +<<< +size: 373 +location: src/tests/client/test-client.py:test_004()/936 +cmd: $NMCLI connection add type ovs-interface ifname ovs-int con-name con-ovs-int ipv4.method auto ipv6.method link-local connection.port-type ovs-port connection.controller ovs-port1 +lang: C +returncode: 0 +stdout: 84 bytes +>>> +Connection 'con-ovs-int' (UUID-con-ovs-int-REPLACED-REPLACED-R) successfully added. + +<<< +size: 377 +location: src/tests/client/test-client.py:test_004()/937 +cmd: $NMCLI connection add type ethernet ifname enp1s0 con-name con-port2 ipv4.method manual ipv4.addresses 192.0.2.1/24 ipv6.method dhcp connection.port-type vrf connection.controller vrf1 +lang: C +returncode: 0 +stdout: 82 bytes +>>> +Connection 'con-port2' (UUID-con-port2-REPLACED-REPLACED-REP) successfully added. + +<<< diff --git a/src/tests/client/test-client.py b/src/tests/client/test-client.py index 62b72b795f..cc76d37b5e 100755 --- a/src/tests/client/test-client.py +++ b/src/tests/client/test-client.py @@ -2125,6 +2125,113 @@ class TestNmcli(unittest.TestCase): replace_stdout=replace_uuids, ) + # It is allowed to set IP method disabled/ignore for ports + replace_uuids.append( + self.ctx.srv.ReplaceTextConUuid( + "con-port1", "UUID-con-port1-REPLACED-REPLACED-REP" + ) + ) + self.call_nmcli( + [ + "connection", + "add", + "type", + "ethernet", + "ifname", + "foobar", + "con-name", + "con-port1", + "ipv4.method", + "disabled", + "ipv6.method", + "ignore", + "connection.port-type", + "bridge", + "connection.controller", + "bridge1", + ], + replace_stdout=replace_uuids, + ) + + # It is NOT allowed to set IP method != disabled/ignore for ports + self.call_nmcli( + [ + "connection", + "add", + "type", + "ethernet", + "ifname", + "foobar", + "con-name", + "ethernet-foobar", + "ipv6.method", + "auto", + "connection.port-type", + "bridge", + "connection.controller", + "bridge1", + ], + replace_stdout=replace_uuids, + ) + + # ovs-interface connections support IP configuration + replace_uuids.append( + self.ctx.srv.ReplaceTextConUuid( + "con-ovs-int", "UUID-con-ovs-int-REPLACED-REPLACED-R" + ) + ) + self.call_nmcli( + [ + "connection", + "add", + "type", + "ovs-interface", + "ifname", + "ovs-int", + "con-name", + "con-ovs-int", + "ipv4.method", + "auto", + "ipv6.method", + "link-local", + "connection.port-type", + "ovs-port", + "connection.controller", + "ovs-port1", + ], + replace_stdout=replace_uuids, + ) + + # VRF ports support IP configuration + replace_uuids.append( + self.ctx.srv.ReplaceTextConUuid( + "con-port2", "UUID-con-port2-REPLACED-REPLACED-REP" + ) + ) + self.call_nmcli( + [ + "connection", + "add", + "type", + "ethernet", + "ifname", + "enp1s0", + "con-name", + "con-port2", + "ipv4.method", + "manual", + "ipv4.addresses", + "192.0.2.1/24", + "ipv6.method", + "dhcp", + "connection.port-type", + "vrf", + "connection.controller", + "vrf1", + ], + replace_stdout=replace_uuids, + ) + @nm_test_no_dbus def test_offline(self): # Make sure we're not using D-Bus diff --git a/tools/test-networkmanager-service.py b/tools/test-networkmanager-service.py index 8d39aef78e..0a13f2bd01 100755 --- a/tools/test-networkmanager-service.py +++ b/tools/test-networkmanager-service.py @@ -613,6 +613,7 @@ class NmUtil: if t not in [ NM.SETTING_GSM_SETTING_NAME, NM.SETTING_MACVLAN_SETTING_NAME, + NM.SETTING_OVS_INTERFACE_SETTING_NAME, NM.SETTING_VLAN_SETTING_NAME, NM.SETTING_VPN_SETTING_NAME, NM.SETTING_WIMAX_SETTING_NAME, From 37c0e0860e7727e1f4a60698861d3a62840e8b82 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 1 Jul 2025 14:26:44 +0200 Subject: [PATCH 35/78] NEWS: update --- NEWS | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 852a0f380a..b243b6a545 100644 --- a/NEWS +++ b/NEWS @@ -15,12 +15,14 @@ NetworkManager-1.54 Overview of changes since NetworkManager-1.52 ============================================= -* Add systemd services to provide networking in the initrd. -* Introduce a new "ovs-dpdk.lsc-interrupt" property to configure the - Link State Change (LSC) detection mode for OVS DPDK interfaces. +* Add support for configuring per-device IPv4 forwarding via the + "ipv4.forwarding" connection property. * Add a new "prefix-delegation" setting containing a "subnet-id" property that specifies the subnet to choose on the downstream interface when using IPv6 prefix delegation. +* When activating a WireGuard connection to an IPv6 endpoint, now + NetworkManager creates firewall rules to ensure that the incoming + packets are not dropped by kernel reverse path filtering. * Add support for configuring the loopback interface in nmtui. * Most of the properties of ovs-bridge and ovs-port connections can now be reapplied at runtime without bringing the connection down. @@ -28,6 +30,11 @@ Overview of changes since NetworkManager-1.52 NetworkManager preserves the SR-IOV parameters set on the device when the connection is deactivated, or whether it resets them to their default value. +* Introduce a new "ovs-dpdk.lsc-interrupt" property to configure the + Link State Change (LSC) detection mode for OVS DPDK interfaces. +* The initrd-generator now can parse the NVMe Boot Firmware Table + (NBFT) to configure networking during early boot. +* Add systemd services to provide networking in the initrd. ============================================= NetworkManager-1.52 From cc5306e1d070d36e3054db8a441b6b54efbdd8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Mon, 7 Jul 2025 09:14:34 +0200 Subject: [PATCH 36/78] NEWS: update --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index b243b6a545..d90768e12b 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,7 @@ Overview of changes since NetworkManager-1.52 * Add a new "prefix-delegation" setting containing a "subnet-id" property that specifies the subnet to choose on the downstream interface when using IPv6 prefix delegation. +* Support OCI baremetal in nm-cloud-setup * When activating a WireGuard connection to an IPv6 endpoint, now NetworkManager creates firewall rules to ensure that the incoming packets are not dropped by kernel reverse path filtering. From 3cba4f262776aa1d6f243e081c65c03a824da7af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8D=C3=B1igo=20Huguet?= Date: Mon, 7 Jul 2025 09:27:05 +0200 Subject: [PATCH 37/78] spec: add git_tag_version The URL to the tarball needs the git tag, that might be 1.54-rc1 instead of 1.53.90. Allow to define it as a separate variable in the spec file. It can be set as `git_tag_version %{real_version}` when they are identical. It is not really needed here in the upstream spec file, as the "Source" line is commented out, but add it as a reference for donwstream spec files. --- contrib/fedora/rpm/NetworkManager.spec | 3 ++- contrib/fedora/rpm/build.sh | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/contrib/fedora/rpm/NetworkManager.spec b/contrib/fedora/rpm/NetworkManager.spec index 788e9ead57..8325152279 100644 --- a/contrib/fedora/rpm/NetworkManager.spec +++ b/contrib/fedora/rpm/NetworkManager.spec @@ -14,6 +14,7 @@ %global epoch_version 1 %global real_version __VERSION__ +%global git_tag_version __GIT_TAG_VERSION__ %global rpm_version %{real_version} %global release_version __RELEASE_VERSION__ %global snapshot __SNAPSHOT__ @@ -180,7 +181,7 @@ Group: System Environment/Base License: GPL-2.0-or-later AND LGPL-2.1-or-later URL: https://networkmanager.dev/ -#Source: https://download.gnome.org/sources/NetworkManager/%{real_version_major}/%{name}-%{real_version}.tar.xz +#Source: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/releases/%{git_tag_version}/downloads/%{name}-%{real_version}.tar.xz Source: __SOURCE1__ Source1: NetworkManager.conf Source2: 00-server.conf diff --git a/contrib/fedora/rpm/build.sh b/contrib/fedora/rpm/build.sh index 4a89a6b45b..1f8188a2f1 100755 --- a/contrib/fedora/rpm/build.sh +++ b/contrib/fedora/rpm/build.sh @@ -12,6 +12,7 @@ set -o pipefail # RELEASE_VERSION= # SNAPSHOT= # VERSION= +# GIT_TAG_VERSION= # COMMIT_FULL= # COMMIT= # USERNAME= @@ -112,6 +113,7 @@ UUID=`uuidgen` RELEASE_VERSION="${RELEASE_VERSION:-$(git rev-list HEAD | wc -l)}" SNAPSHOT="${SNAPSHOT:-%{nil\}}" VERSION="${VERSION:-$(get_version || die "Could not read $VERSION")}" +GIT_TAG_VERSION="${GIT_TAG_VERSION:-$VERSION}" COMMIT_FULL="${COMMIT_FULL:-$(git rev-parse --verify HEAD || die "Error reading HEAD revision")}" COMMIT="${COMMIT:-$(printf '%s' "$COMMIT_FULL" | sed 's/^\(.\{10\}\).*/\1/' || die "Error reading HEAD revision")}" BCOND_DEFAULT_DEBUG="${BCOND_DEFAULT_DEBUG:-0}" @@ -155,6 +157,7 @@ if [[ "$SOURCE_FROM_GIT" == "1" ]]; then fi LOG "VERSION=$VERSION" +LOG "GIT_TAG_VERSION=$GIT_TAG_VERSION" LOG "RELEASE_VERSION=$RELEASE_VERSION" LOG "SNAPSHOT=$SNAPSHOT" LOG "COMMIT_FULL=$COMMIT_FULL" @@ -207,6 +210,7 @@ cp "$SOURCE_README_IFCFG_MIGRATED" "$TEMP/SOURCES/readme-ifcfg-rh-migrated.txt" write_changelog sed -e "s/__VERSION__/$VERSION/g" \ + -e "s/__GIT_TAG_VERSION__/$GIT_TAG_VERSION/g" \ -e "s/__RELEASE_VERSION__/$RELEASE_VERSION/g" \ -e "s/__SNAPSHOT__/$SNAPSHOT/g" \ -e "s/__COMMIT__/$COMMIT/g" \ From 2de6391f4bc90158fd9cba8ef65a5fbaadb3c34f Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 30 Jun 2025 16:48:59 +0200 Subject: [PATCH 38/78] device: drop a redundant assert This is essentially: a = b assert(a == b) Not useful. https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2233 --- src/core/devices/nm-device.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 96cacca78e..7aaf4d78ed 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -13008,7 +13008,6 @@ _dev_addrgenmode6_set(NMDevice *self, guint8 addr_gen_mode) if (!priv->addrgenmode6_data.previous_mode_has) { priv->addrgenmode6_data.previous_mode_has = TRUE; priv->addrgenmode6_data.previous_mode_val = cur_addr_gen_mode; - nm_assert(priv->addrgenmode6_data.previous_mode_val == cur_addr_gen_mode); } _LOGD_ip(AF_INET6, From 9bbb1139872b6a3cb21e1c08c7853057b4ee2674 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 30 Jun 2025 15:50:44 +0200 Subject: [PATCH 39/78] device: don't disable IPv6 in stage3 on reapply Currently, when a call to Reapply() results in stage3 being re-run, IPv6 ends up messed up. Like this: $ nmcli device modify eth0 ipv4.address '' $ nmcli device modify eth0 ipv4.address 172.31.13.37/24 $ NetworkManager[666]: [1751286095.2070] device[c95ca04a69467d81] (eth0): ip4: reapply... ... NetworkManager[666]: [1751286095.2104] device[c95ca04a69467d81] (eth0): ip6: addrgenmode6: set none (already set) NetworkManager[666]: [1751286095.2105] device[c95ca04a69467d81] (eth0): ip6: addrgenmode6: toggle disable_ipv6 sysctl after disabling addr-gen-mode NetworkManager[666]: [1751286095.2105] platform-linux: sysctl: setting '/proc/sys/net/ipv6/conf/eth0/disable_ipv6' to '1' (current value is '0') NetworkManager[666]: [1751286095.2106] platform-linux: sysctl: setting '/proc/sys/net/ipv6/conf/eth0/disable_ipv6' to '0' (current value is '1') NetworkManager[666]: [1751286095.2106] platform-linux: sysctl: setting '/proc/sys/net/ipv6/conf/eth0/accept_ra' to '0' (current value is identical) NetworkManager[666]: [1751286095.2106] platform-linux: sysctl: setting '/proc/sys/net/ipv6/conf/eth0/disable_ipv6' to '0' (current value is identical) Not only is this unnecessary because addr-gen-mode already has the desired value (as is logged), but also wipes off all IPv6 configuration. This is fine on initial configuration, but not on Reapply(). Let's look at the device state first: if we've progressed past ip-config state, then we can't possibly ever touch the offending sysctls. It's okay -- we don't need to: addr-gen-mode is going to be set right if we went through ip-config before. Resolves: https://issues.redhat.com/browse/NMT-1681 https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2232 --- src/core/devices/nm-device.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 7aaf4d78ed..9e0a53cd32 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -13437,7 +13437,8 @@ activate_stage3_ip_config(NMDevice *self) * IPv6LL if this is not an assumed connection, since assumed connections * will already have IPv6 set up. */ - if (!nm_device_managed_type_is_external_or_assume(self)) + if ((priv->state <= NM_DEVICE_STATE_IP_CONFIG || priv->ip_data_6.do_reapply) + && !nm_device_managed_type_is_external_or_assume(self)) _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_NONE); /* Re-enable IPv6 on the interface */ From a9b66e254cb036592cbe2eed87eb41096e215d53 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 2 Jul 2025 16:32:43 +0200 Subject: [PATCH 40/78] nmcli: fix compile error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running the build script with LTO disabled ("contrib/fedora/rpm/build_clean.sh -W lto") gives the following error: In file included from ../src/libnm-std-aux/nm-default-std.h:102, from ../src/libnm-glib-aux/nm-default-glib.h:11, from ../src/libnm-glib-aux/nm-default-glib-i18n-lib.h:13, from ../src/libnm-client-aux-extern/nm-default-client.h:11, from ../src/nmcli/connections.c:6: In function ‘_nm_auto_unref_ptrarray’, inlined from ‘do_connection_add’ at ../src/nmcli/connections.c:6069:35: ../src/libnm-std-aux/nm-std-aux.h:1106:12: error: ‘props’ may be used uninitialized [-Werror=maybe-uninitialized] 1106 | if (*v) \ | ^ ../src/libnm-glib-aux/nm-macros-internal.h:91:1: note: in expansion of macro ‘NM_AUTO_DEFINE_FCN0’ 91 | NM_AUTO_DEFINE_FCN0(GPtrArray *, _nm_auto_unref_ptrarray, g_ptr_array_unref); | ^~~~~~~~~~~~~~~~~~~ ../src/nmcli/connections.c: In function ‘do_connection_add’: ../src/nmcli/connections.c:6069:35: note: ‘props’ was declared here 6069 | gs_unref_ptrarray GPtrArray *props; | ^~~~~ cc1: all warnings being treated as errors Fix it. Fixes: bb850fda0ed9 ('nmcli: connection: process port-type, type and controller first') https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2236 --- src/nmcli/connections.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c index 409f80673f..3f884bc51f 100644 --- a/src/nmcli/connections.c +++ b/src/nmcli/connections.c @@ -6066,7 +6066,7 @@ nmc_add_connection(NmCli *nmc, NMConnection *connection, gboolean temporary) static void do_connection_add(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) { - gs_unref_ptrarray GPtrArray *props; + gs_unref_ptrarray GPtrArray *props = NULL; gs_unref_object NMConnection *connection = NULL; NMSettingConnection *s_con; gs_free_error GError *error = NULL; From 959ddec2a4711813abed7e0999bb5bbc88522e56 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 9 Jul 2025 15:05:39 +0200 Subject: [PATCH 41/78] contrib/nm-vpn-plugin-utils: add nm_vpn_plugin_utils_get_cert_path() Add a function to generate the path for imported certificates. See https://gitlab.gnome.org/GNOME/NetworkManager-openvpn/-/merge_requests/95 --- src/contrib/nm-vpn-plugin-utils.c | 30 ++++++++++++++++++++++++++++++ src/contrib/nm-vpn-plugin-utils.h | 2 ++ 2 files changed, 32 insertions(+) diff --git a/src/contrib/nm-vpn-plugin-utils.c b/src/contrib/nm-vpn-plugin-utils.c index a9407608db..24b08ac6df 100644 --- a/src/contrib/nm-vpn-plugin-utils.c +++ b/src/contrib/nm-vpn-plugin-utils.c @@ -155,3 +155,33 @@ nm_vpn_plugin_utils_load_editor(const char *module_path, g_return_val_if_fail(NM_IS_VPN_EDITOR(editor), NULL); return editor; } + +char * +nm_vpn_plugin_utils_get_cert_path(const char *plugin) +{ + const char *path; + + g_return_val_if_fail(plugin, NULL); + + /* Users can set NM_CERT_PATH=~/.cert to be compatible with the certificate + * directory used in the past. */ + path = g_getenv("NM_CERT_PATH"); + if (path) + return g_build_filename(path, plugin, NULL); + + /* Otherwise use XDG_DATA_HOME. We use subdirectory "networkmanagement/certificates" + * because the SELinux policy already has rules to set the correct labels in that + * directory. */ + path = g_getenv("XDG_DATA_HOME"); + if (path) + return g_build_filename(path, "networkmanagement", "certificates", plugin, NULL); + + /* Use the default value for XDG_DATA_HOME */ + return g_build_filename(g_get_home_dir(), + ".local", + "share", + "networkmanagement", + "certificates", + plugin, + NULL); +} diff --git a/src/contrib/nm-vpn-plugin-utils.h b/src/contrib/nm-vpn-plugin-utils.h index 6a6ea0b99c..78e29d0bf8 100644 --- a/src/contrib/nm-vpn-plugin-utils.h +++ b/src/contrib/nm-vpn-plugin-utils.h @@ -24,4 +24,6 @@ NMVpnEditor *nm_vpn_plugin_utils_load_editor(const char *modul gpointer user_data, GError **error); +char *nm_vpn_plugin_utils_get_cert_path(const char *plugin); + #endif /* __NM_VPN_PLUGIN_UTILS_H__ */ From eff8471de47d08b335264df5eb05ea0f2276a5e8 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 9 Jul 2025 15:07:51 +0200 Subject: [PATCH 42/78] nmcli: fix format Fixes: a9b66e254cb0 ('nmcli: fix compile error') --- src/nmcli/connections.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c index 3f884bc51f..d93e16f03c 100644 --- a/src/nmcli/connections.c +++ b/src/nmcli/connections.c @@ -6066,7 +6066,7 @@ nmc_add_connection(NmCli *nmc, NMConnection *connection, gboolean temporary) static void do_connection_add(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv) { - gs_unref_ptrarray GPtrArray *props = NULL; + gs_unref_ptrarray GPtrArray *props = NULL; gs_unref_object NMConnection *connection = NULL; NMSettingConnection *s_con; gs_free_error GError *error = NULL; From 326fb8f9cfcb3374cbcb520d2701f5ae0ccc4d3b Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 4 Jul 2025 14:28:26 +0200 Subject: [PATCH 43/78] initrd: make parsing of VLANs more robust We are missing some validations when parsing VLANs: a unexpected argument can cause a crash, an assertion, or the connection being dropped without any warning. Make it more robust. --- src/nm-initrd-generator/nmi-cmdline-reader.c | 31 +++++++--- .../tests/test-cmdline-reader.c | 61 +++++++++++++++++++ 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/src/nm-initrd-generator/nmi-cmdline-reader.c b/src/nm-initrd-generator/nmi-cmdline-reader.c index ba5380afb2..89deddf0a3 100644 --- a/src/nm-initrd-generator/nmi-cmdline-reader.c +++ b/src/nm-initrd-generator/nmi-cmdline-reader.c @@ -1063,27 +1063,44 @@ reader_parse_vlan(Reader *reader, char *argument) const char *vlan; const char *phy; const char *vlanid; + guint64 id; vlan = get_word(&argument, ':'); phy = get_word(&argument, ':'); + if (!vlan) { + _LOGW(LOGD_CORE, "missing VLAN interface name"); + return; + } + + if (!phy) { + _LOGW(LOGD_CORE, "missing VLAN parent"); + return; + } + for (vlanid = vlan + strlen(vlan); vlanid > vlan; vlanid--) { if (!g_ascii_isdigit(*(vlanid - 1))) break; } + if (vlanid[0] == '\0') { + _LOGW(LOGD_CORE, "missing VLAN id in '%s'", vlan); + return; + } + + id = _nm_utils_ascii_str_to_int64(vlanid, 10, 0, 4094, G_MAXUINT); + if (id == G_MAXUINT) { + _LOGW(LOGD_CORE, "invalid VLAN id '%s'", vlanid); + return; + } + connection = reader_get_connection(reader, vlan, NM_SETTING_VLAN_SETTING_NAME, TRUE); s_vlan = nm_connection_get_setting_vlan(connection); - g_object_set(s_vlan, - NM_SETTING_VLAN_PARENT, - phy, - NM_SETTING_VLAN_ID, - (guint) _nm_utils_ascii_str_to_int64(vlanid, 10, 0, G_MAXUINT, G_MAXUINT), - NULL); + g_object_set(s_vlan, NM_SETTING_VLAN_PARENT, phy, NM_SETTING_VLAN_ID, (guint32) id, NULL); if (argument && *argument) - _LOGW(LOGD_CORE, "Ignoring extra: '%s'.", argument); + _LOGW(LOGD_CORE, "ignoring extra VLAN argument '%s'", argument); if (!nm_strv_ptrarray_contains(reader->vlan_parents, phy)) g_ptr_array_add(reader->vlan_parents, g_strdup(phy)); diff --git a/src/nm-initrd-generator/tests/test-cmdline-reader.c b/src/nm-initrd-generator/tests/test-cmdline-reader.c index cd7b1069b6..413f61c655 100644 --- a/src/nm-initrd-generator/tests/test-cmdline-reader.c +++ b/src/nm-initrd-generator/tests/test-cmdline-reader.c @@ -1846,6 +1846,66 @@ test_vlan_over_bond(void) } } +static void +test_vlan_invalid(void) +{ + { + /* Case 1: Missing name */ + const char *const *ARGV0 = NM_MAKE_STRV("vlan="); + gs_unref_hashtable GHashTable *connections = NULL; + + NMTST_EXPECT_NM_WARN("cmdline-reader: missing VLAN interface name"); + connections = _parse_cons(ARGV0); + g_assert_cmpint(g_hash_table_size(connections), ==, 0); + g_test_assert_expected_messages(); + } + + { + /* Case 2: Missing parent */ + const char *const *ARGV0 = NM_MAKE_STRV("vlan=vlan12"); + gs_unref_hashtable GHashTable *connections = NULL; + + NMTST_EXPECT_NM_WARN("cmdline-reader: missing VLAN parent"); + connections = _parse_cons(ARGV0); + g_assert_cmpint(g_hash_table_size(connections), ==, 0); + g_test_assert_expected_messages(); + } + + { + /* Case 3: Interface name without trailing digits should fail, + * not trigger a GLib assertion. */ + const char *const *ARGV0 = NM_MAKE_STRV("vlan=myvlan:eth0"); + gs_unref_hashtable GHashTable *connections = NULL; + + NMTST_EXPECT_NM_WARN("cmdline-reader: missing VLAN id in 'myvlan'"); + connections = _parse_cons(ARGV0); + g_assert_cmpint(g_hash_table_size(connections), ==, 0); + g_test_assert_expected_messages(); + } + + { + /* Case 4: An invalid VLAN id should be rejected */ + const char *const *ARGV0 = NM_MAKE_STRV("vlan=myvlan4095:eth0"); + gs_unref_hashtable GHashTable *connections = NULL; + + NMTST_EXPECT_NM_WARN("cmdline-reader: invalid VLAN id '4095'"); + connections = _parse_cons(ARGV0); + g_assert_cmpint(g_hash_table_size(connections), ==, 0); + g_test_assert_expected_messages(); + } + + { + /* Case 5: Extra arguments */ + const char *const *ARGV0 = NM_MAKE_STRV("vlan=eth0.80:eth0:reorder_hdr=on"); + gs_unref_hashtable GHashTable *connections = NULL; + + NMTST_EXPECT_NM_WARN("cmdline-reader: ignoring extra VLAN argument 'reorder_hdr=on'"); + connections = _parse_cons(ARGV0); + g_assert_cmpint(g_hash_table_size(connections), ==, 2); + g_test_assert_expected_messages(); + } +} + static void test_ibft_ip_dev(void) { @@ -2820,6 +2880,7 @@ main(int argc, char **argv) g_test_add_func("/initrd/cmdline/vlan", test_vlan); g_test_add_func("/initrd/cmdline/vlan/dhcp-on-parent", test_vlan_with_dhcp_on_parent); g_test_add_func("/initrd/cmdline/vlan/over-bond", test_vlan_over_bond); + g_test_add_func("/initrd/cmdline/vlan/invalid", test_vlan_invalid); g_test_add_func("/initrd/cmdline/bridge", test_bridge); g_test_add_func("/initrd/cmdline/bridge/default", test_bridge_default); g_test_add_func("/initrd/cmdline/bridge/ip", test_bridge_ip); From 45ab9d96f1afb086447b4ce3540ed78d721b7ff0 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 4 Jul 2025 14:47:31 +0200 Subject: [PATCH 44/78] platform: use g_strdup() instead of strdup() in ethtool code The string is freed with g_free(), it needs to be allocated with g_strdup(). In practice, the GLib allocator uses malloc() nowadays, but it is better to be consistent. --- src/libnm-platform/nmp-ethtool.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libnm-platform/nmp-ethtool.c b/src/libnm-platform/nmp-ethtool.c index ed028f6a1c..28d12ffb1e 100644 --- a/src/libnm-platform/nmp-ethtool.c +++ b/src/libnm-platform/nmp-ethtool.c @@ -153,7 +153,7 @@ ethtool_send_and_recv(struct nl_sock *sock, out: if (nle < 0 && err_msg && *err_msg == NULL) - *err_msg = strdup(nm_strerror(nle)); + *err_msg = g_strdup(nm_strerror(nle)); if (nle >= 0 && cb_result < 0) nle = cb_result; From 253800238eaa062573276db0ea146afc926760a1 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 4 Jul 2025 15:18:27 +0200 Subject: [PATCH 45/78] libnm-core,core: accept uid/gid up to (2^32 - 2) for tun devices Linux UIDs/GIDs are 32-bit unsigned integer, with 4294967295 reserved as undefined. Before: # useradd -u 4294967294 -M testuser useradd warning: testuser's uid -2 outside of the UID_MIN 1000 and UID_MAX 60000 range. # nmcli connection add type tun ifname tun1 owner 4294967294 ipv4.method disabled ipv6.method disabled Error: Failed to add 'tun-tun1' connection: tun.owner: '4294967294': invalid user ID After: # useradd -u 4294967294 -M testuser useradd warning: testuser's uid -2 outside of the UID_MIN 1000 and UID_MAX 60000 range. # nmcli connection add type tun ifname tun1 owner 4294967294 ipv4.method disabled ipv6.method disabled Connection 'tun-tun1' (5da24d19-1723-45d5-8e04-c976f7a251d0) successfully added. # ip -d link show tun1 2421: tun1: mtu 1500 qdisc fq_codel state DOWN mode DEFAULT group default qlen 500 link/none promiscuity 0 allmulti 0 minmtu 68 maxmtu 65535 tun type tun pi off vnet_hdr off persist on user testuser ... ^^^^^^^^^^^^^ Fixes: 1f30147a7a83 ('libnm-core: add NMSettingTun') --- src/core/devices/nm-device-tun.c | 8 +++++--- src/libnm-core-impl/nm-setting-tun.c | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/core/devices/nm-device-tun.c b/src/core/devices/nm-device-tun.c index faab86d0a9..b277c378a2 100644 --- a/src/core/devices/nm-device-tun.c +++ b/src/core/devices/nm-device-tun.c @@ -242,12 +242,14 @@ create_and_realize(NMDevice *device, g_return_val_if_reached(FALSE); } - owner = _nm_utils_ascii_str_to_int64(nm_setting_tun_get_owner(s_tun), 10, 0, G_MAXINT32, -1); + owner = + _nm_utils_ascii_str_to_int64(nm_setting_tun_get_owner(s_tun), 10, 0, G_MAXUINT32 - 1, -1); if (owner != -1) { props.owner_valid = TRUE; props.owner = owner; } - group = _nm_utils_ascii_str_to_int64(nm_setting_tun_get_group(s_tun), 10, 0, G_MAXINT32, -1); + group = + _nm_utils_ascii_str_to_int64(nm_setting_tun_get_group(s_tun), 10, 0, G_MAXUINT32 - 1, -1); if (group != -1) { props.group_valid = TRUE; props.group = group; @@ -278,7 +280,7 @@ _same_og(const char *str, gboolean og_valid, guint32 og_num) { gint64 v; - v = _nm_utils_ascii_str_to_int64(str, 10, 0, G_MAXINT32, -1); + v = _nm_utils_ascii_str_to_int64(str, 10, 0, G_MAXUINT32 - 1, -1); return (!og_valid && (v == (gint64) -1)) || (og_valid && (((guint32) v) == og_num)); } diff --git a/src/libnm-core-impl/nm-setting-tun.c b/src/libnm-core-impl/nm-setting-tun.c index 05460104a4..cac0f10beb 100644 --- a/src/libnm-core-impl/nm-setting-tun.c +++ b/src/libnm-core-impl/nm-setting-tun.c @@ -166,7 +166,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error) } if (priv->owner) { - if (_nm_utils_ascii_str_to_int64(priv->owner, 10, 0, G_MAXINT32, -1) == -1) { + if (_nm_utils_ascii_str_to_int64(priv->owner, 10, 0, G_MAXUINT32 - 1, -1) == -1) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, @@ -178,7 +178,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error) } if (priv->group) { - if (_nm_utils_ascii_str_to_int64(priv->group, 10, 0, G_MAXINT32, -1) == -1) { + if (_nm_utils_ascii_str_to_int64(priv->group, 10, 0, G_MAXUINT32 - 1, -1) == -1) { g_set_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY, From 55765d29147b48fa2295a547a8aba0556f60cb59 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 4 Jul 2025 16:59:43 +0200 Subject: [PATCH 46/78] ovs: fix logging message Fixes: a259303e1d5a ('ovs: add support for "other_config" settings') --- src/core/devices/ovs/nm-ovsdb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c index 74fcd80bc8..164bfd3565 100644 --- a/src/core/devices/ovs/nm-ovsdb.c +++ b/src/core/devices/ovs/nm-ovsdb.c @@ -2249,7 +2249,7 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg) ovs_bridge->connection_uuid, ""), (strtmp1 = _strdict_to_string(ovs_bridge->external_ids)), - (strtmp2 = _strdict_to_string(ovs_bridge->external_ids))); + (strtmp2 = _strdict_to_string(ovs_bridge->other_config))); } } else { gs_free char *strtmp1 = NULL; From 62558d50be377f670c85416d2268b55d5ac82c77 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 4 Jul 2025 18:04:56 +0200 Subject: [PATCH 47/78] core: fix constant name for IPv4 method Fix the name for consistency, even if this is harmless because the IPv4 and IPv6 values are the same. --- src/core/devices/nm-device-ethernet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c index 4034fdaad6..3bc74d80e2 100644 --- a/src/core/devices/nm-device-ethernet.c +++ b/src/core/devices/nm-device-ethernet.c @@ -1894,7 +1894,7 @@ get_ip_method_auto(NMDevice *device, int addr_family) /* We cannot do DHCPv4 on a PPP link, instead we get "auto" IP addresses * by pppd. Return "manual" here, which has the suitable effect to a * (zero) manual addresses in addition. */ - return NM_SETTING_IP6_CONFIG_METHOD_MANUAL; + return NM_SETTING_IP4_CONFIG_METHOD_MANUAL; } return NM_SETTING_IP6_CONFIG_METHOD_AUTO; From 404a3ec8531482e0db8ef39b75ec1ed9d9b04be7 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 4 Jul 2025 18:26:59 +0200 Subject: [PATCH 48/78] core: fix properties update for HSR devices Fixes: 5426bdf4a122 ('HSR: add support to HSR/PRP interface') --- src/core/devices/nm-device-hsr.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/devices/nm-device-hsr.c b/src/core/devices/nm-device-hsr.c index 8156fa7796..59454ee341 100644 --- a/src/core/devices/nm-device-hsr.c +++ b/src/core/devices/nm-device-hsr.c @@ -94,8 +94,10 @@ update_properties(NMDevice *device) CHECK_PROPERTY_CHANGED(multicast_spec, PROP_MULTICAST_SPEC); CHECK_PROPERTY_CHANGED(prp, PROP_PRP); - if (!nm_ether_addr_equal(&priv->props.supervision_address, &props->supervision_address)) + if (!nm_ether_addr_equal(&priv->props.supervision_address, &props->supervision_address)) { + priv->props.supervision_address = props->supervision_address; _notify(self, PROP_SUPERVISION_ADDRESS); + } g_object_thaw_notify((GObject *) device); } From 1229fe5abdc907407196fd557fee9e0a31f04c0d Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 4 Jul 2025 18:31:42 +0200 Subject: [PATCH 49/78] bond: fix attribute assignment macro Currently the bug is hidden because the macro is only called with NM_SETTING_BOND_OPTION_ARP_IP_TARGET. Fixes: 45c95e9314cd ('device/bond: rework setting of arp_ip_target bond options') --- src/core/devices/nm-device-bond.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c index 0915e9156a..673d236194 100644 --- a/src/core/devices/nm-device-bond.c +++ b/src/core/devices/nm-device-bond.c @@ -137,13 +137,13 @@ _set_bond_attr(NMDevice *device, const char *attr, const char *value) return ret; } -#define _set_bond_attr_take(device, attr, value) \ - G_STMT_START \ - { \ - gs_free char *_tmp = (value); \ - \ - _set_bond_attr(device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, _tmp); \ - } \ +#define _set_bond_attr_take(device, attr, value) \ + G_STMT_START \ + { \ + gs_free char *_tmp = (value); \ + \ + _set_bond_attr(device, attr, _tmp); \ + } \ G_STMT_END #define _set_bond_attr_printf(device, attr, fmt, ...) \ From 20a1d7e8163d00b837b552b7b477a0c631c90f3a Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 4 Jul 2025 18:37:48 +0200 Subject: [PATCH 50/78] vrf: fix wrong logging domain Fixes: 667568d1b2be ('core,libnm: add VRF support') --- src/core/devices/nm-device-vrf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/devices/nm-device-vrf.c b/src/core/devices/nm-device-vrf.c index 7dfd6504a1..6d272a95cc 100644 --- a/src/core/devices/nm-device-vrf.c +++ b/src/core/devices/nm-device-vrf.c @@ -235,7 +235,7 @@ attach_port(NMDevice *device, _LOGI(LOGD_DEVICE, "attached VRF port %s", port_iface); } else - _LOGI(LOGD_BOND, "VRF port %s was attached", port_iface); + _LOGI(LOGD_DEVICE, "VRF port %s was attached", port_iface); return TRUE; } From ce17284c3f71322101f9dcc2272405c533dafc51 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 4 Jul 2025 22:14:32 +0200 Subject: [PATCH 51/78] lldp: fix memchr() argument order The validation of embedded NUL character was skipped due to the wrong order of arguments to memchr(). Fix it. Fixes: 4043f8279003 ('lldp: cleanup converting binary LLDP fields to string') --- src/core/devices/nm-lldp-listener.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/devices/nm-lldp-listener.c b/src/core/devices/nm-lldp-listener.c index e08b379adf..5c90911fc3 100644 --- a/src/core/devices/nm-lldp-listener.c +++ b/src/core/devices/nm-lldp-listener.c @@ -310,7 +310,7 @@ format_string(const guint8 *data, gsize len, gboolean allow_trim, char **out_to_ if (len == 0) return NULL; - if (memchr(data, len, '\0')) + if (memchr(data, '\0', len)) return NULL; return nm_utils_buf_utf8safe_escape(data, From 50a400e16f1a98b2c22ac6dad74102a5da91a04a Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 09:55:14 +0200 Subject: [PATCH 52/78] supplicant: fix wrong check on "EAP" signal arguments The check is inverted. Fixes: b83f07916a54 ('supplicant: large rework of wpa_supplicant handling') --- src/core/supplicant/nm-supplicant-interface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c index d5f56dcde4..514b7c0d39 100644 --- a/src/core/supplicant/nm-supplicant-interface.c +++ b/src/core/supplicant/nm-supplicant-interface.c @@ -3082,7 +3082,7 @@ _signal_handle(NMSupplicantInterface *self, const char *status; const char *parameter; - if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(ss)"))) + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(ss)"))) return; g_variant_get(parameters, "(&s&s)", &status, ¶meter); From 43f738473cee66744775a829c79720d97099fe93 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 09:56:29 +0200 Subject: [PATCH 53/78] core: remove duplicate include --- src/core/devices/nm-device-ethernet.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c index 3bc74d80e2..2059ce4279 100644 --- a/src/core/devices/nm-device-ethernet.c +++ b/src/core/devices/nm-device-ethernet.c @@ -14,7 +14,6 @@ #include #include -#include "NetworkManagerUtils.h" #include "NetworkManagerUtils.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "libnm-core-intern/nm-core-internal.h" From a9d7abbc5023b51a6e4e8a5e2644fd1a2a20c838 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 09:32:16 +0200 Subject: [PATCH 54/78] dhcp: fix parsing of the search list option The DHCP search list option (119) can use the "message compression" algorithm specified in RFC 1035 section 4.1.4 to reduce the size of the message in presence of subdomains that appear multiple times. When using the compression a label starts with: +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | 1 1| OFFSET | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ where the offset points to a previous domain. Previously, the parsing code was taking the lower 6 bits of the first byte, shifting them left 16 bits, and adding the next byte. Instead, the shift should be of 8 bits. The effect of this bug was that when the offset was greater than 255, it was incorrectly parsed as a number larger than the message size, and the parsing failed. Note that while a single DHCP option can be at most 255 bytes, a DHCP message can contain multiple instances of the same option. The receiver must concatenate all the occurrences according to RFC 3396 and parse the resulting buffer. Fixes: 6adade6f21d5 ('dhcp: add nettools dhcp4 client') --- src/core/dhcp/nm-dhcp-utils.c | 2 +- src/core/dhcp/tests/test-dhcp-utils.c | 70 +++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/core/dhcp/nm-dhcp-utils.c b/src/core/dhcp/nm-dhcp-utils.c index 15293fa389..949d87207f 100644 --- a/src/core/dhcp/nm-dhcp-utils.c +++ b/src/core/dhcp/nm-dhcp-utils.c @@ -1224,7 +1224,7 @@ lease_option_print_domain_name(const uint8_t *cache, } case 0xC0: /* back pointer */ { - size_t offset = (c & 0x3F) << 16; + size_t offset = (c & 0x3F) << 8; /* * The offset is given as two bytes (in big endian), where the diff --git a/src/core/dhcp/tests/test-dhcp-utils.c b/src/core/dhcp/tests/test-dhcp-utils.c index b81523e19b..de1be6538c 100644 --- a/src/core/dhcp/tests/test-dhcp-utils.c +++ b/src/core/dhcp/tests/test-dhcp-utils.c @@ -239,6 +239,76 @@ test_parse_search_list(void) g_assert_cmpint(g_strv_length(domains), ==, 1); g_assert_cmpstr(domains[0], ==, "okay"); g_strfreev(domains); + + /* Test that the message compression works when the offset uses both bytes */ + data = (guint8[]) { + /* clang-format off */ + /* offset 0 */ + 0x3e, + 'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', + 'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', + 'a','a','a','a','a','a','a','a','a','a','a','a','a','a','a','a', + 'a','a','a','a','a','a','a','a','a','a','a','a','a','a', + 0x00, + /* offset 0x40 */ + 0x3e, + 'b','b','b','b','b','b','b','b','b','b','b','b','b','b','b','b', + 'b','b','b','b','b','b','b','b','b','b','b','b','b','b','b','b', + 'b','b','b','b','b','b','b','b','b','b','b','b','b','b','b','b', + 'b','b','b','b','b','b','b','b','b','b','b','b','b','b', + 0x00, + /* offset 0x80 */ + 0x3e, + 'c','c','c','c','c','c','c','c','c','c','c','c','c','c','c','c', + 'c','c','c','c','c','c','c','c','c','c','c','c','c','c','c','c', + 'c','c','c','c','c','c','c','c','c','c','c','c','c','c','c','c', + 'c','c','c','c','c','c','c','c','c','c','c','c','c','c', + 0x00, + /* offset 0xc0 */ + 0x3e, + 'd','d','d','d','d','d','d','d','d','d','d','d','d','d','d','d', + 'd','d','d','d','d','d','d','d','d','d','d','d','d','d','d','d', + 'd','d','d','d','d','d','d','d','d','d','d','d','d','d','d','d', + 'd','d','d','d','d','d','d','d','d','d','d','d','d','d', + 0x00, + /* offset 0x100 */ + 0x3e, + 'e','e','e','e','e','e','e','e','e','e','e','e','e','e','e','e', + 'e','e','e','e','e','e','e','e','e','e','e','e','e','e','e','e', + 'e','e','e','e','e','e','e','e','e','e','e','e','e','e','e','e', + 'e','e','e','e','e','e','e','e','e','e','e','e','e','e', + 0x00, + /* offset 0x140 */ + 0x06, 'f','o','o','b','a','r', 0x03, 'c', 'o', 'm', 0x00, + 0x04, 't', 'e', 's', 't', 0xc1, 0x40, /* back pointer to offset 0x140*/ + /* clang-format on */ + }; + + domains = nm_dhcp_lease_data_parse_search_list(data, + 0x153, + "eth0", + AF_INET, + NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST); + g_assert(domains); + g_assert_cmpint(g_strv_length(domains), ==, 7); + g_assert_cmpstr(domains[0], + ==, + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + g_assert_cmpstr(domains[1], + ==, + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + g_assert_cmpstr(domains[2], + ==, + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"); + g_assert_cmpstr(domains[3], + ==, + "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"); + g_assert_cmpstr(domains[4], + ==, + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); + g_assert_cmpstr(domains[5], ==, "foobar.com"); + g_assert_cmpstr(domains[6], ==, "test.foobar.com"); + g_strfreev(domains); } static void From d017dc67b42a1060b9e892d4dc14f461711fda23 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 09:58:43 +0200 Subject: [PATCH 55/78] dhcp: fix typo in tcp_keepalive_interval option name Fixes: eed205bff317 ('dhcp/internal: move dhcp options management to shared dhcp codebase') --- src/core/dhcp/nm-dhcp-options.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/dhcp/nm-dhcp-options.c b/src/core/dhcp/nm-dhcp-options.c index ce03c60771..30cfd8e68f 100644 --- a/src/core/dhcp/nm-dhcp-options.c +++ b/src/core/dhcp/nm-dhcp-options.c @@ -68,7 +68,7 @@ const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = { REQ(NM_DHCP_OPTION_DHCP4_ARP_CACHE_TIMEOUT, "arp_cache_timeout", FALSE), REQ(NM_DHCP_OPTION_DHCP4_IEEE802_3_ENCAPSULATION, "ieee802_3_encapsulation", FALSE), REQ(NM_DHCP_OPTION_DHCP4_DEFAULT_TCP_TTL, "default_tcp_ttl", FALSE), - REQ(NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_INTERVAL, "tcp_keepalive_internal", FALSE), + REQ(NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_INTERVAL, "tcp_keepalive_interval", FALSE), REQ(NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_GARBAGE, "tcp_keepalive_garbage", FALSE), REQ(NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, "vendor_encapsulated_options", FALSE), REQ(NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER, "netbios_name_servers", FALSE), From 7d23ed9f73abec2d6f5b760e094284d4a239931e Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 09:38:45 +0200 Subject: [PATCH 56/78] platform: rename nm_linux_platform_get_link_fdb_table() Rename nm_linux_platform_get_link_fdb_table() to nm_linux_platform_get_bridge_fdb(). The new name better indicates that the function returns the bridge FDB entries. --- src/core/nm-bond-manager.c | 2 +- src/libnm-platform/nm-linux-platform.c | 2 +- src/libnm-platform/nm-linux-platform.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/nm-bond-manager.c b/src/core/nm-bond-manager.c index 116cb73744..a29ce679d6 100644 --- a/src/core/nm-bond-manager.c +++ b/src/core/nm-bond-manager.c @@ -920,7 +920,7 @@ nm_bond_manager_send_arp(int bond_ifindex, int i; gs_free NMEtherAddr **fdb_addrs = NULL; - fdb_addrs = nm_linux_platform_get_link_fdb_table(platform, ifindexes, 2); + fdb_addrs = nm_linux_platform_get_bridge_fdb(platform, ifindexes, 2); /* we want to send a Reverse ARP (RARP) packet */ data.op = htons(ARP_OP_RARP); diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index 2cc50f8f77..f796e53dfb 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -10519,7 +10519,7 @@ parse_fdb_cb(const struct nl_msg *msg, void *arg) } NMEtherAddr ** -nm_linux_platform_get_link_fdb_table(NMPlatform *platform, int *ifindexes, guint ifindexes_len) +nm_linux_platform_get_bridge_fdb(NMPlatform *platform, int *ifindexes, guint ifindexes_len) { int nle; struct nl_sock *sk = NULL; diff --git a/src/libnm-platform/nm-linux-platform.h b/src/libnm-platform/nm-linux-platform.h index 3f591b7f1a..c7f1b17a2b 100644 --- a/src/libnm-platform/nm-linux-platform.h +++ b/src/libnm-platform/nm-linux-platform.h @@ -26,7 +26,7 @@ GType nm_linux_platform_get_type(void); struct _NMDedupMultiIndex; NMEtherAddr ** -nm_linux_platform_get_link_fdb_table(NMPlatform *platform, int *ifindexes, guint ifindexes_len); +nm_linux_platform_get_bridge_fdb(NMPlatform *platform, int *ifindexes, guint ifindexes_len); NMPlatform *nm_linux_platform_new(struct _NMDedupMultiIndex *multi_idx, gboolean log_with_ptr, From 16ef33d38095a9fab694a28deee9059a6510e1df Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 09:43:12 +0200 Subject: [PATCH 57/78] bond-slb: fix memory leak If sendto() fails, the function returns and the remaining entries are not deallocated. Use nm_auto_freev instead to free the array and the pointer it contains. Add a test to check that nm_auto_freev does the right thing on the value returned by nm_linux_platform_get_bridge_fdb(). Fixes: 3f2f922dd943 ('bonding: send ARP announcement on bonding-slb link/carrier down') --- src/core/nm-bond-manager.c | 8 ++-- src/core/platform/tests/test-link.c | 62 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/core/nm-bond-manager.c b/src/core/nm-bond-manager.c index a29ce679d6..abaab7d4f3 100644 --- a/src/core/nm-bond-manager.c +++ b/src/core/nm-bond-manager.c @@ -916,9 +916,9 @@ nm_bond_manager_send_arp(int bond_ifindex, if (announce_fdb) { /* if we are announcing the FDB we do a RARP, we don't set the * source/dest IPv4 address */ - int ifindexes[] = {bridge_ifindex, bond_ifindex}; - int i; - gs_free NMEtherAddr **fdb_addrs = NULL; + int ifindexes[] = {bridge_ifindex, bond_ifindex}; + int i; + nm_auto_freev NMEtherAddr **fdb_addrs = NULL; fdb_addrs = nm_linux_platform_get_bridge_fdb(platform, ifindexes, 2); /* we want to send a Reverse ARP (RARP) packet */ @@ -927,10 +927,10 @@ nm_bond_manager_send_arp(int bond_ifindex, i = 0; while (fdb_addrs[i] != NULL) { NMEtherAddr *tmp_hwaddr = fdb_addrs[i]; + memcpy(data.s_hw_addr, tmp_hwaddr, ETH_ALEN); memcpy(data.d_hw_addr, tmp_hwaddr, ETH_ALEN); memcpy(data.s_addr, tmp_hwaddr, ETH_ALEN); - g_free(tmp_hwaddr); if (sendto(sockfd, &data, sizeof(data), 0, (struct sockaddr *) &addr, sizeof(addr)) < 0) return FALSE; i++; diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c index 4a08d565a0..54d9dd7fb8 100644 --- a/src/core/platform/tests/test-link.c +++ b/src/core/platform/tests/test-link.c @@ -2725,6 +2725,66 @@ test_link_set_properties(void) /*****************************************************************************/ +static void +test_link_get_bridge_fdb(void) +{ + const NMPlatformLink *link; + nm_auto_freev NMEtherAddr **addrs; + int ifindex[2]; + guint8 expected[][6] = { + {0x00, 0x99, 0x00, 0x00, 0x00, 0x01}, + {0x00, 0x99, 0x00, 0x00, 0x00, 0x02}, + {0x00, 0x99, 0x00, 0x00, 0x00, 0x03}, + {0x00, 0x99, 0x00, 0x00, 0x00, 0x05}, + }; + guint i; + guint j; + + ifindex[0] = + nmtstp_link_bridge_add(NULL, -1, "br-test-1", &nm_platform_lnk_bridge_default)->ifindex; + ifindex[1] = + nmtstp_link_bridge_add(NULL, -1, "br-test-2", &nm_platform_lnk_bridge_default)->ifindex; + + link = nmtstp_link_get(NULL, ifindex[0], "br-test-1"); + g_assert(link); + link = nmtstp_link_get(NULL, ifindex[1], "br-test-2"); + g_assert(link); + + nmtstp_run_command_check("bridge fdb add dev br-test-1 00:99:00:00:00:01"); + nmtstp_run_command_check("bridge fdb add dev br-test-1 00:99:00:00:00:02"); + nmtstp_run_command_check("bridge fdb add dev br-test-1 00:99:00:00:00:03"); + nmtstp_run_command_check("bridge fdb add dev br-test-2 00:99:00:00:00:01"); + nmtstp_run_command_check("bridge fdb add dev br-test-2 00:99:00:00:00:05"); + + addrs = nm_linux_platform_get_bridge_fdb(NM_PLATFORM_GET, ifindex, 2); + g_assert(addrs); + + /* Check for expected entries */ + for (i = 0; i < G_N_ELEMENTS(expected); i++) { + gboolean found = FALSE; + + for (j = 0; addrs[j]; j++) { + if (memcmp(addrs[j], expected[i], ETH_ALEN) == 0) { + found = TRUE; + break; + } + } + g_assert(found); + } + + /* No dupes */ + for (i = 0; addrs[i]; i++) { + for (j = i + 1; addrs[j]; j++) { + g_assert_cmpint(memcmp(addrs[i], addrs[j], ETH_ALEN), !=, 0); + } + } + + nmtstp_link_delete(NULL, -1, ifindex[0], "br-test-1", TRUE); + nmtstp_link_delete(NULL, -1, ifindex[1], "br-test-2", TRUE); +} + +/*****************************************************************************/ + static void test_create_many_links_do(guint n_devices) { @@ -4106,6 +4166,8 @@ _nmtstp_setup_tests(void) test_software_detect_add("/link/software/detect/wireguard/1", NM_LINK_TYPE_WIREGUARD, 1); test_software_detect_add("/link/software/detect/wireguard/2", NM_LINK_TYPE_WIREGUARD, 2); + g_test_add_func("/link/get-bridge-fdb", test_link_get_bridge_fdb); + g_test_add_func("/link/software/vlan/set-xgress", test_vlan_set_xgress); g_test_add_func("/link/set-properties", test_link_set_properties); From b58a37acfe5c8ea7bb4ee43c03f591bd4d024ca1 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 10:00:29 +0200 Subject: [PATCH 58/78] core: fix nm_utils_get_nm_gid() Fixes: 31dbcb81fe7a ('core: make nm_utils_get_nm_[ug]id() thread safe') --- src/core/nm-core-utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c index 895a99177d..ebadde3c7b 100644 --- a/src/core/nm-core-utils.c +++ b/src/core/nm-core-utils.c @@ -5417,7 +5417,7 @@ again: if ((g = g_atomic_int_get(&g_static)) == -1) { gid_t g2; - g2 = geteuid(); + g2 = getegid(); g = g2; nm_assert(g == g2); nm_assert(g >= 0); From fa80896ee7c130158844fb209365dbace68db5a4 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 09:49:33 +0200 Subject: [PATCH 59/78] core: fix Wi-Fi data rate tables Fixes: f2b0092b5b23 ('wifi: parse BSS IEs for 80211n and 80211ac data rates') --- src/core/nm-core-utils.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c index ebadde3c7b..d3c2eb21ca 100644 --- a/src/core/nm-core-utils.c +++ b/src/core/nm-core-utils.c @@ -4642,13 +4642,13 @@ get_max_rate_vht_80_ss3(int mcs) case 5: return 702000000; case 6: - return 0; + return 0; /* invalid */ case 7: return 877500000; case 8: - return 105300000; + return 1053000000; case 9: - return 117000000; + return 1170000000; } return 0; } @@ -4732,7 +4732,7 @@ get_max_rate_vht_160_ss3(int mcs) case 8: return 2106000000; case 9: - return 0; + return 0; /* invalid */ } return 0; } From f4f1ecc7eaabc65bfe6d94eb1751c7cdaf9a4c89 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 09:15:01 +0200 Subject: [PATCH 60/78] libnm-core: fix p-key validation for Infiniband connections verify() is setting an error without returning FALSE to make the validation fail. When the parent is set, the device is a Infiniband partition and it must have a p-key != -1. Fixes: d595f7843e31 ('libnm: add libnm/libnm-core (part 1)') --- src/libnm-core-impl/nm-setting-infiniband.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libnm-core-impl/nm-setting-infiniband.c b/src/libnm-core-impl/nm-setting-infiniband.c index 24e5807acb..b70247c1c6 100644 --- a/src/libnm-core-impl/nm-setting-infiniband.c +++ b/src/libnm-core-impl/nm-setting-infiniband.c @@ -212,6 +212,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error) NM_CONNECTION_ERROR_INVALID_PROPERTY, _("Must specify a P_Key if specifying parent")); g_prefix_error(error, "%s: ", NM_SETTING_INFINIBAND_PARENT); + return FALSE; } } From 104cafdd44df643fbdbc79f2bff6b3cd039ca4b9 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 09:22:58 +0200 Subject: [PATCH 61/78] libnm-core: fix documentation for NMSettingMatch functions Fixes: 3a8e46f2a59a ('settings: add match for proc cmdline') --- src/libnm-core-impl/nm-setting-match.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libnm-core-impl/nm-setting-match.c b/src/libnm-core-impl/nm-setting-match.c index 255283fc57..dc743b159a 100644 --- a/src/libnm-core-impl/nm-setting-match.c +++ b/src/libnm-core-impl/nm-setting-match.c @@ -309,9 +309,10 @@ nm_setting_match_clear_kernel_command_lines(NMSettingMatch *setting) * @setting: the #NMSettingMatch * @length: (out) (optional): the length of the returned interface names array. * - * Returns all the interface names. + * Returns all the kernel command line arguments. * - * Returns: (transfer none) (array length=length): the configured interface names. + * Returns: (transfer none) (array length=length): the configured kernel command + * line arguments. * * Since: 1.26 **/ @@ -344,7 +345,7 @@ nm_setting_match_get_num_drivers(NMSettingMatch *setting) /** * nm_setting_match_get_driver: * @setting: the #NMSettingMatch - * @idx: index number of the DNS search domain to return + * @idx: index number of the driver to return * * Since 1.46, access at index "len" is allowed and returns NULL. * @@ -443,7 +444,7 @@ nm_setting_match_clear_drivers(NMSettingMatch *setting) /** * nm_setting_match_get_drivers: * @setting: the #NMSettingMatch - * @length: (out) (optional): the length of the returned interface names array. + * @length: (out) (optional): the length of the returned drivers array. * * Returns all the drivers. * From fdb8f07c447b9ce5f4af8ff8662c8b9ce08e52e5 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 09:27:17 +0200 Subject: [PATCH 62/78] libnm-core: validate the ipvlan mode The setting must reject unknown ipvlan modes. Fixes: d238ff487b29 ('ipvlan: add support to IPVLAN interface') --- src/libnm-core-impl/nm-setting-ipvlan.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libnm-core-impl/nm-setting-ipvlan.c b/src/libnm-core-impl/nm-setting-ipvlan.c index fafa37b6ee..0468e31fd5 100644 --- a/src/libnm-core-impl/nm-setting-ipvlan.c +++ b/src/libnm-core-impl/nm-setting-ipvlan.c @@ -156,6 +156,19 @@ verify(NMSetting *setting, NMConnection *connection, GError **error) } } + if (!NM_IN_SET(priv->mode, + NM_SETTING_IPVLAN_MODE_L2, + NM_SETTING_IPVLAN_MODE_L3, + NM_SETTING_IPVLAN_MODE_L3S)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("unsupported mode %u"), + priv->mode); + g_prefix_error(error, "%s.%s: ", NM_SETTING_IPVLAN_SETTING_NAME, NM_SETTING_IPVLAN_MODE); + return FALSE; + } + if (priv->private_flag && priv->vepa) { g_set_error(error, NM_CONNECTION_ERROR, From b45d5f41dd7948512f33c42e2e501d6ea5ca3110 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 09:31:15 +0200 Subject: [PATCH 63/78] platform: fix harmless typo The function should modify the "ip6_address" member of the union. In practice, it doesn't matter because the ifindex is the first member of both "ip4_address" and "ip6_address". --- src/libnm-platform/nmp-object.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c index e11bfb3f5b..56533c99c5 100644 --- a/src/libnm-platform/nmp-object.c +++ b/src/libnm-platform/nmp-object.c @@ -893,7 +893,7 @@ const NMPObject * nmp_object_stackinit_id_ip6_address(NMPObject *obj, int ifindex, const struct in6_addr *address) { _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_IP6_ADDRESS); - obj->ip4_address.ifindex = ifindex; + obj->ip6_address.ifindex = ifindex; if (address) obj->ip6_address.address = *address; return obj; From eb7917a387741085c888fad6e764e61e77e54016 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 7 Jul 2025 16:53:27 +0200 Subject: [PATCH 64/78] ndisc: fix logic to limit the number of addresses Fixes: c2c8c67d8c45 ('ndisc: rate limit number of accepted RA data to track') --- src/core/ndisc/nm-ndisc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c index 3083de009d..1a2bf48072 100644 --- a/src/core/ndisc/nm-ndisc.c +++ b/src/core/ndisc/nm-ndisc.c @@ -1490,7 +1490,7 @@ clean_addresses(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint g_array_set_size(rdata->addresses, j); } - if (_array_set_size_max(rdata->gateways, priv->config.max_addresses)) + if (_array_set_size_max(rdata->addresses, priv->config.max_addresses)) *changed |= NM_NDISC_CONFIG_ADDRESSES; } From 74cf2a2bd8899c481d7c3aec4fac949c1b3db22c Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 10 Jul 2025 10:04:36 +0200 Subject: [PATCH 65/78] l3cfg: fix logging message Fix spacing in: acd[192.168.122.42, probing]: probing currently stillnot possible ^^^^^^^^^ Fixes: b8f9d7b5dd11070ecb51b1aa5f4a4f6ad15fc0b5 --- src/core/nm-l3cfg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index d2c1294b6b..8a69a28483 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -3050,7 +3050,7 @@ handle_start_probing: if (!acd_data->nacd_probe) { _LOGT_acd(acd_data, "probing currently %snot possible (timeout %u msec; %s, %s)", - orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : " still", + orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : "still ", acd_data->probing_timeout_msec, failure_reason, log_reason); From f584524197755f76ae900e60411956de15b717ea Mon Sep 17 00:00:00 2001 From: Filip Pokryvka Date: Fri, 11 Jul 2025 08:33:22 +0200 Subject: [PATCH 66/78] release: bump version to 1.55.2 (development) --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 6125afa53d..628fcd4346 100644 --- a/meson.build +++ b/meson.build @@ -5,7 +5,7 @@ project( # NOTE: When incrementing version also add corresponding # NM_VERSION_x_y_z macros in # "src/libnm-core-public/nm-version-macros.h.in" - version: '1.55.1', + version: '1.55.2', license: 'GPL2+', default_options: [ 'buildtype=debugoptimized', From f655cd1cbac35cbd93571020df43662972812eb3 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Wed, 2 Jul 2025 15:27:01 +0200 Subject: [PATCH 67/78] man: fix nmcli connection types list Update the nmcli man page to accurately reflect the connection types supported by NetworkManager: - Remove ovs-dpdk and ovs-patch (not supported by nmcli) - Add hsr, ipvlan, and loopback (missing from the documentation) This ensures the man page matches the actual connection types returned by 'nmcli --complete-args connection add type'. https://issues.redhat.com/browse/RHEL-100893 https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2235 --- man/nmcli.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/man/nmcli.xml b/man/nmcli.xml index f3b6706ece..7bce0de737 100644 --- a/man/nmcli.xml +++ b/man/nmcli.xml @@ -1066,15 +1066,16 @@ dummy generic gsm + hsr infiniband ip-tunnel + ipvlan + loopback macsec macvlan olpc-mesh ovs-bridge - ovs-dpdk ovs-interface - ovs-patch ovs-port pppoe team From bf79fbd6780fd38ca29c12a137951c8729379767 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 8 Jul 2025 15:09:25 +0200 Subject: [PATCH 68/78] bridge: fix reapplying port VLANs If the bridge default-pvid is zero, it means that the default PVID is disabled. That is, the bridge PVID is not propagated to ports. Currently NM tries to merge the existing bridge VLANs on the port with the default PVID from the bridge, even when the PVID is zero. This causes an error when setting the new VLAN list in the kernel, because it rejects VLAN zero. Skip the merge of the default PVID when zero. Fixes: c5d1e35f993e ('device: support reapplying bridge-port VLANs') --- src/core/devices/nm-device-bridge.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c index 7c34fde07f..45cdd83f07 100644 --- a/src/core/devices/nm-device-bridge.c +++ b/src/core/devices/nm-device-bridge.c @@ -735,6 +735,11 @@ merge_bridge_vlan_default_pvid(NMPlatformBridgeVlan *vlans, guint *num_vlans, gu gboolean has_pvid = FALSE; guint i; + if (default_pvid == 0) { + /* default_pvid=0 means that the default PVID is disabled. No need to merge it. */ + return vlans; + } + for (i = 0; i < *num_vlans; i++) { if (vlans[i].pvid) { has_pvid = TRUE; From 261fa8db336e0571479567e2bda10dbf5d171b0a Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 8 Jul 2025 17:16:25 +0200 Subject: [PATCH 69/78] device: accept changes to the bond-port.vlans during reapply Commit c5d1e35f993e ('device: support reapplying bridge-port VLANs') didn't update can_reapply_change() to accept the "bridge-port.vlans" property during a reapply. So, it was only possible to change the bridge port VLANs by updating the "bridge.vlan-default-pvid" property and doing a reapply. Fix that. Fixes: c5d1e35f993e ('device: support reapplying bridge-port VLANs') --- src/core/devices/nm-device.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 9e0a53cd32..c30d01814c 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -14050,6 +14050,13 @@ can_reapply_change(NMDevice *self, goto out_fail; } + if (nm_streq(setting_name, NM_SETTING_BRIDGE_PORT_SETTING_NAME)) { + return nm_device_hash_check_invalid_keys(diffs, + NM_SETTING_BRIDGE_PORT_SETTING_NAME, + error, + NM_SETTING_BRIDGE_PORT_VLANS); + } + if (nm_streq(setting_name, NM_SETTING_SRIOV_SETTING_NAME)) { return nm_device_hash_check_invalid_keys(diffs, NM_SETTING_SRIOV_SETTING_NAME, From b4a22ad2a9b7cf411161248f29c2d588cee11dea Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 17 Jul 2025 10:28:27 +0200 Subject: [PATCH 70/78] platform: fix compilation without LTO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the following error seen when running the build_clean.sh script with LTO disabled: In file included from ../src/libnm-glib-aux/nm-default-glib.h:66, from ../src/libnm-glib-aux/nm-default-glib-i18n-prog.h:13, from ../src/core/nm-default-daemon.h:11, from ../src/core/platform/tests/test-link.c:6: In function ‘_nm_auto_freev’, inlined from ‘test_link_get_bridge_fdb’ at ../src/core/platform/tests/test-link.c:2732:33: ../src/libnm-glib-aux/nm-macros-internal.h:166:8: error: ‘addrs’ may be used uninitialized [-Werror=maybe-uninitialized] 166 | if (*p) { | ^ ../src/core/platform/tests/test-link.c: In function ‘test_link_get_bridge_fdb’: ../src/core/platform/tests/test-link.c:2732:33: note: ‘addrs’ was declared here 2732 | nm_auto_freev NMEtherAddr **addrs; | ^~~~~ cc1: all warnings being treated as errors Fixes: 16ef33d38095 ('bond-slb: fix memory leak') --- src/core/platform/tests/test-link.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c index 54d9dd7fb8..fab6bd2efe 100644 --- a/src/core/platform/tests/test-link.c +++ b/src/core/platform/tests/test-link.c @@ -2729,7 +2729,7 @@ static void test_link_get_bridge_fdb(void) { const NMPlatformLink *link; - nm_auto_freev NMEtherAddr **addrs; + nm_auto_freev NMEtherAddr **addrs = NULL; int ifindex[2]; guint8 expected[][6] = { {0x00, 0x99, 0x00, 0x00, 0x00, 0x01}, From b019883a9a001da856de9629c5d35ca4646b197c Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 17 Jul 2025 18:30:30 +0200 Subject: [PATCH 71/78] core: accept hostnames longer than 64 characters from DNS lookup When resolving the system hostname from DNS lookup, we use nm_utils_validate_hostname() which checks that the result is a valid hostname. A valid hostname is at most 64 characters on Linux. Anything longer is discarded. However, the reverse DNS lookup doesn't return a hostname, it returns a DNS name. The DNS name can have multiple labels, each limited to 63 characters. The maximum length of the DNS name is 253 characters. If the result is longer than 64 characters because it has multiple labels, we should still accept it, provided that it is a valid DNS name. Then when setting the hostname in the system, only the first label will be kept. https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2243 Resolves: https://issues.redhat.com/browse/RHEL-104357 --- src/core/devices/nm-device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index c30d01814c..fd0eb7dba6 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -18690,7 +18690,7 @@ hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer use gboolean valid; resolver->hostname = g_steal_pointer(&output); - valid = nm_utils_validate_hostname(resolver->hostname); + valid = nm_sd_dns_name_is_valid(resolver->hostname); _LOGD(LOGD_DNS, "hostname-from-dns: ipv%c resolver %s: lookup successful for %s, result %s%s%s%s", From 407d753a5a70bc12b2e1426ef2833c1abe945de9 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 11 Jul 2025 15:47:05 +0200 Subject: [PATCH 72/78] l3cfg: don't reset the ACD probe timestamp during timer events acd_data->probing_timestamp_msec indicates when the probing started. It is used in different places to calculate the timeout for certain operations. In particular, it is used to detect that the probe creation took too long when handling the ACD_STATE_CHANGE_MODE_TIMEOUT event. If we reset this timestamp at every timer event, we'll never hit the probe creation timeout. Therefore, the l3cfg will keep trying forever to create the probe. See: https://lists.freedesktop.org/archives/networkmanager/2025-July/000418.html Fix this by not updating the timestamp during a timeout event. Fixes: a09f9cc61629 ('l3cfg: ensure the probing timeout is initialized on probe start') --- src/core/nm-l3cfg.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 8a69a28483..1797cd0f93 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -2751,9 +2751,8 @@ handle_init: goto handle_start_defending; } - acd_data->probing_timestamp_msec = (*p_now_msec); - acd_data->probing_timeout_msec = acd_timeout_msec; - log_reason = "retry probing on timeout"; + acd_data->probing_timeout_msec = acd_timeout_msec; + log_reason = "retry probing on timeout"; goto handle_start_probing; } From 127f73a5c2d1816eb4f40d740940854414eae23c Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 11 Jul 2025 21:09:10 +0200 Subject: [PATCH 73/78] l3cfg: fix the interval of the ACD restart timer After ACD_WAIT_PROBING_EXTRA_TIME_MSEC has elapsed, _l3_acd_data_timeout_schedule_probing_restart() keeps rescheduling the timer with a zero interval, resulting in 100% CPU usage. This continues until the probe is destroyed after ACD_WAIT_PROBING_EXTRA_TIME2_MSEC. When computing the interval, we need to use (ACD_WAIT_PROBING_EXTRA_TIME_MSEC + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC) as the expiry time. --- src/core/nm-l3cfg.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 1797cd0f93..54ca2cdd31 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -2322,7 +2322,8 @@ _l3_acd_data_timeout_schedule_probing_restart(AcdData *acd_data, gint64 now_msec nm_assert(acd_data->probing_timeout_msec > 0); nm_assert(acd_data->probing_timestamp_msec > 0); - expiry_msec = acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC; + expiry_msec = acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC + + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC; timeout_msec = NM_MAX(0, expiry_msec - now_msec); From eb0a41ce1f9066c90576bd3bafecfde245237fe4 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Mon, 14 Jul 2025 09:47:13 +0200 Subject: [PATCH 74/78] l3cfg: simplify the ACD timeouts ACD_WAIT_PROBING_EXTRA_TIME_MSEC and ACD_WAIT_PROBING_EXTRA_TIME2_MSEC now are always used together. Consolidate them into a single constant. --- src/core/nm-l3cfg.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 54ca2cdd31..35dc81cff9 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -40,8 +40,7 @@ G_STATIC_ASSERT(NM_ACD_TIMEOUT_RFC5227_MSEC == N_ACD_TIMEOUT_RFC5227); #define ACD_SUPPORTED_ETH_ALEN ETH_ALEN #define ACD_ENSURE_RATELIMIT_MSEC ((guint32) 4000u) -#define ACD_WAIT_PROBING_EXTRA_TIME_MSEC ((guint32) (1000u + ACD_ENSURE_RATELIMIT_MSEC)) -#define ACD_WAIT_PROBING_EXTRA_TIME2_MSEC ((guint32) 1000u) +#define ACD_WAIT_PROBING_EXTRA_TIME_MSEC ((guint32) (2000u + ACD_ENSURE_RATELIMIT_MSEC)) #define ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC ((guint32) 30000u) #define ACD_WAIT_TIME_CONFLICT_RESTART_MSEC ((guint32) 120000u) #define ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC ((guint32) 30000u) @@ -2322,8 +2321,7 @@ _l3_acd_data_timeout_schedule_probing_restart(AcdData *acd_data, gint64 now_msec nm_assert(acd_data->probing_timeout_msec > 0); nm_assert(acd_data->probing_timestamp_msec > 0); - expiry_msec = acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC - + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC; + expiry_msec = acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC; timeout_msec = NM_MAX(0, expiry_msec - now_msec); @@ -2741,9 +2739,8 @@ handle_init: nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING) { - if ((*p_now_msec) > acd_data->probing_timestamp_msec - + ACD_WAIT_PROBING_EXTRA_TIME_MSEC - + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC) { + if ((*p_now_msec) + > acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC) { /* hm. We failed to create a new probe too long. Something is really wrong * internally, but let's ignore the issue and assume the address is good. What * else would we do? Assume the address is USED? */ @@ -2949,7 +2946,7 @@ handle_init: nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); if (acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec - + ACD_WAIT_PROBING_EXTRA_TIME_MSEC + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC + + ACD_WAIT_PROBING_EXTRA_TIME_MSEC >= (*p_now_msec)) { /* The probing already started quite a while ago. We ignore the link event * and let the probe come to it's natural end. */ From 4938507de8f0ecfc532e6c4d6e37b52e17ee0f65 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Tue, 22 Jul 2025 10:09:50 +0200 Subject: [PATCH 75/78] dhcp6: fix accessor macro for t2 sd_dhcp6_lease_get_t2() was returning t1, and so sometimes the client was going directly to the rebind state skipping the lease renewal. See the systemd PR: https://github.com/systemd/systemd/pull/38275 Fixes: a14a033efbaa ('systemd: update code from upstream (2024-02-12)') https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2246 --- src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9569af58a8..ecbc763338 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 @@ -114,7 +114,7 @@ static void dhcp6_lease_set_lifetime(sd_dhcp6_lease *lease) { } DEFINE_GET_TIME_FUNCTIONS(t1, lifetime_t1); -DEFINE_GET_TIME_FUNCTIONS(t2, lifetime_t1); +DEFINE_GET_TIME_FUNCTIONS(t2, lifetime_t2); DEFINE_GET_TIME_FUNCTIONS(valid_lifetime, lifetime_valid); static void dhcp6_lease_set_server_address(sd_dhcp6_lease *lease, const struct in6_addr *server_address) { From 394f6281eadbca5cf51386be854bb73086cadc09 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Sun, 13 Jul 2025 18:12:58 +0200 Subject: [PATCH 76/78] platform: fix GError free function Fixes: dd7810c473c9 ('platform: destroy VFs before changing the eswitch mode') --- src/libnm-platform/nm-linux-platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index f796e53dfb..c7c78265c6 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -9249,7 +9249,7 @@ sriov_async_finish_err(SriovAsyncState *async_state, GError *error) g_object_unref(async_state->platform); g_object_unref(async_state->cancellable); g_free(async_state); - g_free(error); + nm_g_error_free(error); } static void From 012f1cbfacf1bac9fd9888b68ee48adbae9e7085 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 18 Jul 2025 09:02:52 +0200 Subject: [PATCH 77/78] device: fix signal emission on port detach/release The "notify::controller" signal must be emitted on the port, not on the controller. Fixes: 1f05526ed77e ('core: drop NMDevice master and introduce controller') --- src/core/devices/nm-device.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index fd0eb7dba6..198e75ec70 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -9029,7 +9029,7 @@ nm_device_port_notify_attach_as_port(NMDevice *self, gboolean success) priv->is_attached = TRUE; - _notify(priv->controller, PROP_CONTROLLER); + _notify(self, PROP_CONTROLLER); nm_clear_pointer(&NM_DEVICE_GET_PRIVATE(priv->controller)->ports_variant, g_variant_unref); @@ -9108,7 +9108,7 @@ nm_device_port_notify_release(NMDevice *self, priv->is_attached = FALSE; - _notify(priv->controller, PROP_CONTROLLER); + _notify(self, PROP_CONTROLLER); nm_clear_pointer(&NM_DEVICE_GET_PRIVATE(priv->controller)->ports_variant, g_variant_unref); nm_gobject_notify_together(priv->controller, PROP_PORTS, PROP_SLAVES); From ec996135a93bc58c199d703f40a4e0b7f658a070 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 18 Jul 2025 09:25:28 +0200 Subject: [PATCH 78/78] device: fix comment in attach_port() --- src/core/devices/nm-device-bridge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c index 45cdd83f07..6ca3737f9d 100644 --- a/src/core/devices/nm-device-bridge.c +++ b/src/core/devices/nm-device-bridge.c @@ -1066,7 +1066,7 @@ attach_port(NMDevice *device, plat_vlans = setting_vlans_to_platform(vlans, &num_vlans); - /* Since the link was just enportd, there are no existing VLANs + /* Since the link was just attached, there are no existing VLANs * (except for the default one) and so there's no need to flush. */ if (plat_vlans