diff --git a/src/libnm-systemd-core/meson.build b/src/libnm-systemd-core/meson.build index 15e1c8a62b..00d8a77baf 100644 --- a/src/libnm-systemd-core/meson.build +++ b/src/libnm-systemd-core/meson.build @@ -12,6 +12,7 @@ libnm_systemd_core = static_library( 'src/libsystemd-network/sd-dhcp6-lease.c', 'src/libsystemd/sd-device/device-private.c', 'src/libsystemd/sd-device/sd-device.c', + 'src/libsystemd/sd-device/device-util.c', 'src/libsystemd/sd-event/event-util.c', 'src/libsystemd/sd-event/sd-event.c', 'src/libsystemd/sd-id128/id128-util.c', 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 0373269288..1c695a8700 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c +++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c @@ -6,13 +6,13 @@ #include "nm-sd-adapt-core.h" #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 5655c0d747..a7cd6a24b9 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 @@ -6,11 +6,11 @@ #include "nm-sd-adapt-core.h" #include -#include #ifdef __GLIBC__ #include #endif #include +#include #include "sd-dhcp6-client.h" @@ -1295,7 +1295,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 6b17e7724a..aa73184d1b 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 @@ -119,6 +119,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; @@ -137,6 +141,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; @@ -150,7 +158,9 @@ int device_get_devnode_uid(sd_device *device, uid_t *ret) { return 0; } -static int device_set_devuid(sd_device *device, const char *uid) { +#endif /* NM_IGNORED */ + +int device_set_devuid(sd_device *device, const char *uid) { uid_t u; int r; @@ -175,6 +185,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; @@ -188,7 +202,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; @@ -208,6 +222,8 @@ static int device_set_devgid(sd_device *device, const char *gid) { return 0; } +#if 0 /* NM_IGNORED */ + int device_set_action(sd_device *device, sd_device_action_t a) { int r; @@ -432,10 +448,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; @@ -682,8 +699,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++; } @@ -691,7 +708,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++; @@ -955,8 +972,4 @@ 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); -} #endif /* NM_IGNORED */ 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..e07ca6026b 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,11 @@ 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); +#if 0 /* NM_IGNORED */ +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); +#endif /* NM_IGNORED */ void device_seal(sd_device *device); void device_set_is_initialized(sd_device *device); @@ -76,4 +81,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..25290e630a --- /dev/null +++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.c @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-shared.h" + +#include "device-private.h" +#include "device-util.h" +#include "devnum-util.h" +#include "fd-util.h" +#include "string-util.h" +#include "strv.h" + +#if 0 /* NM_IGNORED */ +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); +} + +#endif /* NM_IGNORED */ + +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 480223d08c..13368493d7 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 @@ -375,7 +375,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; @@ -516,7 +516,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; } @@ -776,16 +776,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")) @@ -797,89 +805,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 = 0; 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; @@ -904,6 +882,21 @@ _public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) { } #if 0 /* NM_IGNORED */ +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; @@ -1228,36 +1221,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", @@ -1369,23 +1355,21 @@ 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); + 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) @@ -1497,6 +1481,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); @@ -1726,18 +1730,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) { @@ -1755,13 +1754,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; } @@ -2355,134 +2359,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) { @@ -2556,19 +2650,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); @@ -2576,52 +2673,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; } @@ -2634,10 +2722,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); @@ -2650,16 +2736,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( @@ -2673,10 +2750,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; @@ -2691,7 +2764,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 ac986e4897..f8c374b95e 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 @@ -4,12 +4,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, @@ -157,19 +166,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) { @@ -180,4 +243,91 @@ 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; +} #endif /* NM_IGNORED */ 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 c0db014f8f..692184db3f 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, @@ -38,5 +41,11 @@ int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handle #if 0 /* NM_IGNORED */ 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); #endif /* NM_IGNORED */ 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 b345b14522..4f04e05835 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 @@ -5,6 +5,7 @@ #include #include #include +#include #include "sd-daemon.h" #include "sd-event.h" @@ -26,12 +27,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" @@ -45,11 +45,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; } @@ -434,7 +433,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; } @@ -992,7 +991,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)) @@ -1092,12 +1091,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); @@ -1107,10 +1105,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); } } @@ -1903,15 +1898,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; @@ -2740,9 +2735,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; } @@ -3009,13 +3006,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) { @@ -3208,9 +3205,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; } @@ -3219,51 +3213,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; } @@ -3272,9 +3241,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; @@ -3733,9 +3700,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 @@ -3746,6 +3713,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; @@ -3765,23 +3733,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); @@ -3802,6 +3768,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; @@ -3812,14 +3779,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); @@ -4232,15 +4204,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 6d515a94db..406e6f2269 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 @@ -11,6 +11,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 9f6ef719c6..8f3cd8c452 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 @@ -4,6 +4,7 @@ #include #include +#include #include #include "sd-id128.h" @@ -16,16 +17,16 @@ #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" #if 0 /* NM_IGNORED */ -_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); @@ -41,7 +42,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); @@ -221,8 +222,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/meson.build b/src/libnm-systemd-shared/meson.build index 7a88d78d96..2719954647 100644 --- a/src/libnm-systemd-shared/meson.build +++ b/src/libnm-systemd-shared/meson.build @@ -30,6 +30,7 @@ libnm_systemd_shared = static_library( 'src/basic/locale-util.c', 'src/basic/memory-util.c', 'src/basic/mempool.c', + 'src/basic/mountpoint-util.c', 'src/basic/ordered-set.c', 'src/basic/parse-util.c', 'src/basic/path-util.c', @@ -49,6 +50,7 @@ libnm_systemd_shared = static_library( 'src/basic/time-util.c', 'src/basic/tmpfile-util.c', 'src/basic/utf8.c', + 'src/basic/user-util.c', 'src/fundamental/sha256-fundamental.c', 'src/fundamental/string-util-fundamental.c', 'src/shared/dns-domain.c', diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.c b/src/libnm-systemd-shared/src/basic/alloc-util.c index f968666676..a2c0caa165 100644 --- a/src/libnm-systemd-shared/src/basic/alloc-util.c +++ b/src/libnm-systemd-shared/src/basic/alloc-util.c @@ -8,7 +8,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..308605945a --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/assert-util.h @@ -0,0 +1,48 @@ +/* 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_; + +#if 0 /* NM_IGNORED */ +void log_assert_failed_return(const char *text, const char *file, int line, const char *func); +#else /* NM_IGNORED */ +#define log_assert_failed_return(text, file, line, func) \ + ({ \ + log_internal(LOG_DEBUG, \ + 0, \ + file, \ + line, \ + func, \ + "Assertion '%s' failed at %s:%u, function %s(). Ignoring.", \ + text, \ + file, \ + line, \ + func); \ + g_return_if_fail_warning(G_LOG_DOMAIN, G_STRFUNC, text); \ + (void) 0; \ + }) +#endif /* NM_IGNORED */ + +#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..269d68c5c6 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, @@ -365,4 +357,4 @@ typedef union { .file_handle.handle_type = FILEID_KERNFS, \ } -#define CG_FILE_HANDLE_CGROUPID(fh) (*(uint64_t*) (fh).file_handle.f_handle) +#define CG_FILE_HANDLE_CGROUPID(fh) (*CAST_ALIGN_PTR(uint64_t, (fh).file_handle.f_handle)) diff --git a/src/libnm-systemd-shared/src/basic/chattr-util.c b/src/libnm-systemd-shared/src/basic/chattr-util.c index 2a63e46d3f..0a27e02313 100644 --- a/src/libnm-systemd-shared/src/basic/chattr-util.c +++ b/src/libnm-systemd-shared/src/basic/chattr-util.c @@ -6,13 +6,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 75b2febf7d..d705765b0d 100644 --- a/src/libnm-systemd-shared/src/basic/env-file.c +++ b/src/libnm-systemd-shared/src/basic/env-file.c @@ -9,6 +9,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 2fe7332c43..8dd52c09c8 100644 --- a/src/libnm-systemd-shared/src/basic/env-util.c +++ b/src/libnm-systemd-shared/src/basic/env-util.c @@ -13,6 +13,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" @@ -549,7 +550,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; @@ -700,7 +701,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; @@ -722,33 +723,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; @@ -1121,7 +1113,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; @@ -1145,25 +1137,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 e882aced8c..ff1d817c1b 100644 --- a/src/libnm-systemd-shared/src/basic/escape.c +++ b/src/libnm-systemd-shared/src/basic/escape.c @@ -82,10 +82,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) @@ -99,12 +104,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; @@ -452,7 +451,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); @@ -482,13 +481,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 2643c9749a..168fd32937 100644 --- a/src/libnm-systemd-shared/src/basic/ether-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.c @@ -10,6 +10,7 @@ #include "ether-addr-util.h" #include "hexdecoct.h" +#include "log.h" #include "macro.h" #include "string-util.h" @@ -87,22 +88,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 e052a6a26c..fd6a95e052 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.c +++ b/src/libnm-systemd-shared/src/basic/fd-util.c @@ -4,9 +4,7 @@ #include #include -#if WANT_LINUX_FS_H -#include -#endif +#include #include #include #include @@ -19,6 +17,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" @@ -31,7 +30,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. */ @@ -1012,13 +1010,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) { @@ -1095,30 +1093,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); @@ -1126,11 +1121,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); @@ -1138,15 +1133,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 78231228ca..0d83faf278 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.c +++ b/src/libnm-systemd-shared/src/basic/fileio.c @@ -247,17 +247,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; @@ -280,9 +276,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; @@ -296,24 +289,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); @@ -323,37 +319,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) @@ -369,9 +367,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); @@ -383,7 +378,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; @@ -445,7 +440,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); @@ -457,7 +451,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; @@ -478,7 +472,13 @@ int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_ext } #endif /* NM_IGNORED */ -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; @@ -495,11 +495,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; @@ -623,31 +635,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, @@ -727,7 +714,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; @@ -907,74 +894,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 8159299893..57f39c30c6 100644 --- a/src/libnm-systemd-shared/src/basic/format-ifname.c +++ b/src/libnm-systemd-shared/src/basic/format-ifname.c @@ -3,6 +3,8 @@ #include "nm-sd-adapt-shared.h" #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 b2a5d2d048..6b767ef23c 100644 --- a/src/libnm-systemd-shared/src/basic/format-util.c +++ b/src/libnm-systemd-shared/src/basic/format-util.c @@ -64,5 +64,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 0202933df6..bdc341f640 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.c +++ b/src/libnm-systemd-shared/src/basic/fs-util.c @@ -3,11 +3,11 @@ #include "nm-sd-adapt-shared.h" #include +#include +#include #include #include #include -#include -#include #include #include "alloc-util.h" @@ -80,6 +80,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; @@ -792,7 +797,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; @@ -1041,7 +1046,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 @@ -1264,7 +1269,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 d38698b688..aedd47c23d 100644 --- a/src/libnm-systemd-shared/src/basic/glyph-util.c +++ b/src/libnm-systemd-shared/src/basic/glyph-util.c @@ -25,7 +25,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 @@ -34,133 +34,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 d20ca3c336..b1975cfe49 100644 --- a/src/libnm-systemd-shared/src/basic/hash-funcs.c +++ b/src/libnm-systemd-shared/src/basic/hash-funcs.c @@ -12,15 +12,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; @@ -61,12 +69,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); @@ -76,23 +87,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); @@ -102,7 +109,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 0 /* NM_IGNORED */ #if SIZEOF_DEV_T != 8 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 6b0247c338..e58375c919 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.c +++ b/src/libnm-systemd-shared/src/basic/hashmap.c @@ -14,6 +14,7 @@ #include "alloc-util.h" #include "fileio.h" #include "hashmap.h" +#include "log.h" #include "logarithm.h" #include "macro.h" #include "memory-util.h" @@ -916,24 +917,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 @@ -945,11 +942,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); } } @@ -1784,7 +1781,7 @@ HashmapBase* _hashmap_copy(HashmapBase *h HASHMAP_DEBUG_PARAMS) { } if (r < 0) - return _hashmap_free(copy, NULL, NULL); + return _hashmap_free(copy); return copy; } @@ -1809,6 +1806,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 26e196f35e..eed3bf0aba 100644 --- a/src/libnm-systemd-shared/src/basic/hostname-util.c +++ b/src/libnm-systemd-shared/src/basic/hostname-util.c @@ -12,18 +12,22 @@ #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" #if 0 /* NM_IGNORED */ -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); } @@ -32,48 +36,14 @@ 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; -} #endif /* NM_IGNORED */ bool valid_ldh_char(char c) { @@ -120,7 +90,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; @@ -163,7 +133,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 44445f1e43..b7a64b1821 100644 --- a/src/libnm-systemd-shared/src/basic/in-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/in-addr-util.c @@ -15,6 +15,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" @@ -30,7 +31,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->s6_addr32); } int in_addr_is_null(int family, const union in_addr_union *u) { @@ -68,7 +69,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->s6_addr32[0] & htobe32(0xffc00000)) == htobe32(0xfe800000); } int in_addr_is_link_local(int family, const union in_addr_union *u) { @@ -102,7 +103,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->s6_addr[0] == 0xff; } int in_addr_is_multicast(int family, const union in_addr_union *u) { @@ -138,6 +139,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); @@ -145,7 +150,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; } @@ -158,7 +163,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; } @@ -180,7 +185,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) { @@ -951,7 +956,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 abe61ed56c..706535a10d 100644 --- a/src/libnm-systemd-shared/src/basic/io-util.c +++ b/src/libnm-systemd-shared/src/basic/io-util.c @@ -90,6 +90,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; @@ -194,7 +195,7 @@ int pipe_eof(int fd) { } #endif /* NM_IGNORED */ -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); @@ -214,10 +215,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 0654fde835..d491d81f46 100644 --- a/src/libnm-systemd-shared/src/basic/locale-util.c +++ b/src/libnm-systemd-shared/src/basic/locale-util.c @@ -19,8 +19,9 @@ #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" @@ -28,7 +29,7 @@ #include "utf8.h" #if 0 /* NM_IGNORED */ -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 @@ -96,18 +97,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; @@ -120,11 +118,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 || @@ -157,9 +156,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; } @@ -168,6 +167,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; @@ -183,7 +184,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; } @@ -191,11 +192,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; @@ -216,31 +216,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); @@ -287,64 +281,48 @@ int locale_is_installed(const char *name) { } #endif /* NM_IGNORED */ -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; } #if 0 /* NM_IGNORED */ 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 6477bff449..4741f58030 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; @@ -90,13 +87,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. */ -#if 0 /* NM_IGNORED */ -assert_cc(STRLEN(__FILE__) > STRLEN(RELATIVE_SOURCE_PATH) + 1); -#define PROJECT_FILE (&__FILE__[STRLEN(RELATIVE_SOURCE_PATH) + 1]) -#else /* NM_IGNORED */ -#define PROJECT_FILE __FILE__ -#endif /* NM_IGNORED */ - bool stderr_is_journal(void); int log_open(void); void log_close(void); @@ -224,7 +214,7 @@ int log_struct_internal( const char *file, int line, const char *func, - const char *format, ...) _printf_(6,0) _sentinel_; + const char *format, ...) _sentinel_; #endif /* NM_IGNORED */ #if 0 /* NM_IGNORED */ @@ -293,53 +283,6 @@ _noreturn_ void log_assert_failed( G_STMT_END #endif /* NM_IGNORED */ -#if 0 /* NM_IGNORED */ -_noreturn_ void log_assert_failed_unreachable( - const char *file, - int line, - const char *func); -#else /* NM_IGNORED */ -#define log_assert_failed_unreachable(file, line, func) \ - G_STMT_START \ - { \ - log_internal(LOG_CRIT, \ - 0, \ - file, \ - line, \ - func, \ - "Code should not be reached at %s:%u, function %s(). Aborting.", \ - file, \ - line, \ - func); \ - g_assert_not_reached(); \ - } \ - G_STMT_END -#endif /* NM_IGNORED */ - -#if 0 /* NM_IGNORED */ -void log_assert_failed_return( - const char *text, - const char *file, - int line, - const char *func); -#else /* NM_IGNORED */ -#define log_assert_failed_return(text, file, line, func) \ - ({ \ - log_internal(LOG_DEBUG, \ - 0, \ - file, \ - line, \ - func, \ - "Assertion '%s' failed at %s:%u, function %s(). Ignoring.", \ - text, \ - file, \ - line, \ - func); \ - g_return_if_fail_warning(G_LOG_DOMAIN, G_STRFUNC, text); \ - (void) 0; \ - }) -#endif /* NM_IGNORED */ - #if 0 /* NM_IGNORED */ #define log_dispatch(level, error, buffer) \ log_dispatch_internal(level, error, PROJECT_FILE, __LINE__, __func__, NULL, NULL, NULL, NULL, buffer) @@ -450,11 +393,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 */ @@ -471,9 +418,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); #if 0 /* NM_IGNORED */ @@ -540,58 +484,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); @@ -601,112 +493,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 789e96a9c3..3e9a3683ff 100644 --- a/src/libnm-systemd-shared/src/basic/memory-util.c +++ b/src/libnm-systemd-shared/src/basic/memory-util.c @@ -2,10 +2,11 @@ #include "nm-sd-adapt-shared.h" +#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; @@ -42,7 +43,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); @@ -57,3 +58,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 e467d7065e..60a89a6bda 100644 --- a/src/libnm-systemd-shared/src/basic/mempool.c +++ b/src/libnm-systemd-shared/src/basic/mempool.c @@ -6,6 +6,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..280e233b26 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. @@ -79,6 +21,7 @@ * are hexa and others are octal; duplicated as-is from the kernel definitions): * - alpha, arm, arm64, m68k, mips, parisc, powerpc, sparc: each has a specific value; * - others: they use the "generic" value (defined in include/uapi/asm-generic/fcntl.h) */ +#if 0 /* NM_IGNORED */ #if O_LARGEFILE != 0 #define RAW_O_LARGEFILE O_LARGEFILE #else @@ -96,7 +39,15 @@ #define RAW_O_LARGEFILE 00100000 #endif #endif +#endif /* NM_IGNORED */ +/* 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 18a15ab00a..0000000000 --- a/src/libnm-systemd-shared/src/basic/missing_stat.h +++ /dev/null @@ -1,139 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include -#include - -#if 0 /* NM_IGNORED */ -#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; -#else /* NM_IGNORED */ -struct new_statx; -#endif /* NM_IGNORED */ - -/* 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 da6f9827f0..55475f7e33 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,26 +18,15 @@ #include "macro.h" #include "missing_keyctl.h" #include "missing_sched.h" -#include "missing_stat.h" #include "missing_syscall_def.h" #if 0 /* NM_IGNORED */ - -/* 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 @@ -80,95 +64,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 -#endif /* NM_IGNORED */ - -/* ======================================================================= */ - -#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 - -/* ======================================================================= */ - -#if 0 /* NM_IGNORED */ -/* 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); @@ -186,12 +81,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); @@ -201,34 +100,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 @@ -236,28 +112,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, @@ -269,14 +123,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 @@ -286,14 +133,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 @@ -304,6 +144,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); } @@ -311,7 +152,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); } @@ -321,28 +165,10 @@ 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 0 /* NM_IGNORED */ #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 @@ -351,27 +177,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. */ @@ -379,10 +199,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 @@ -390,243 +206,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); } @@ -653,16 +234,39 @@ int __clone2(int (*fn)(void *), void *stack_base, size_t stack_size, int flags, #if 0 /* NM_IGNORED */ #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 #endif /* NM_IGNORED */ 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..7d7ef5eb73 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/mountpoint-util.c @@ -0,0 +1,823 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-shared.h" + +#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); +} + +#if 0 /* NM_IGNORED */ +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"); +} +#endif /* NM_IGNORED */ \ No newline at end of file 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 bff09b7416..e524d9d52d 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.c +++ b/src/libnm-systemd-shared/src/basic/parse-util.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,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" @@ -533,7 +535,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; @@ -784,19 +786,15 @@ 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 "/\\_."); } #endif /* NM_IGNORED */ 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 1de055d540..74f256a69f 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.c +++ b/src/libnm-systemd-shared/src/basic/path-util.c @@ -624,37 +624,9 @@ char* path_extend_internal(char **x, ...) { } #if 0 /* NM_IGNORED */ -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); @@ -665,23 +637,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) @@ -703,7 +692,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) { @@ -718,7 +707,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) @@ -754,7 +743,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 9b13d958db..5bfa8fc7d4 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.h +++ b/src/libnm-systemd-shared/src/basic/path-util.h @@ -117,6 +117,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 d6bd551758..91638c770a 100644 --- a/src/libnm-systemd-shared/src/basic/pidfd-util.c +++ b/src/libnm-systemd-shared/src/basic/pidfd-util.c @@ -3,15 +3,18 @@ #include "nm-sd-adapt-shared.h" #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" @@ -89,26 +92,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")) @@ -230,6 +228,7 @@ int pidfd_get_cgroupid(int fd, uint64_t *ret) { #endif /* NM_IGNORED */ int pidfd_get_inode_id(int fd, uint64_t *ret) { + static bool file_handle_supported = true; int r; assert(fd >= 0); @@ -240,6 +239,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 = *CAST_ALIGN_PTR(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; @@ -247,6 +270,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 c36d0ddd38..0a007a29e5 100644 --- a/src/libnm-systemd-shared/src/basic/prioq.c +++ b/src/libnm-systemd-shared/src/basic/prioq.c @@ -48,6 +48,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); } @@ -180,6 +185,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) @@ -191,6 +201,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; @@ -254,6 +265,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 8518756222..517990b5c9 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.c +++ b/src/libnm-systemd-shared/src/basic/process-util.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #if 0 /* NM_IGNORED */ #if HAVE_VALGRIND_VALGRIND_H @@ -38,13 +39,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" @@ -55,6 +56,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" @@ -509,48 +511,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()) { @@ -558,7 +522,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) { @@ -591,14 +562,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) { @@ -864,15 +845,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) @@ -881,27 +859,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); } /* @@ -918,24 +877,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)); @@ -958,6 +922,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: * @@ -1385,8 +1353,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 { @@ -1446,7 +1414,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 @@ -1467,7 +1435,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; @@ -1533,25 +1501,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; @@ -1594,14 +1565,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; } } @@ -1619,8 +1619,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); @@ -1638,16 +1660,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; @@ -1809,36 +1846,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; } @@ -2013,17 +2055,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 19ac55ddf7..395df64ae5 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); @@ -195,20 +207,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], @@ -221,6 +222,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 1e1a24bdfa..dab4334ad9 100644 --- a/src/libnm-systemd-shared/src/basic/random-util.c +++ b/src/libnm-systemd-shared/src/basic/random-util.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "alloc-util.h" #include "env-util.h" @@ -21,9 +22,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 05b2b50d42..6722e4bcda 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.c +++ b/src/libnm-systemd-shared/src/basic/signal-util.c @@ -4,11 +4,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" @@ -283,13 +283,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 9cb0d7f46c..7c83551cfa 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.c +++ b/src/libnm-systemd-shared/src/basic/socket-util.c @@ -2,11 +2,12 @@ #include "nm-sd-adapt-shared.h" -/* Make sure the net/if.h header is included before any linux/ one */ -#include #include #include #include +#include +#include +#include #include #include #include @@ -16,9 +17,6 @@ #include #include #include -#if 0 /* NM_IGNORED */ -#include -#endif /* NM_IGNORED */ #include "alloc-util.h" #include "errno-util.h" @@ -32,6 +30,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" @@ -1361,6 +1360,53 @@ void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, } #if 0 /* NM_IGNORED */ +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; @@ -1457,25 +1503,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) { @@ -1815,8 +1879,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 b6c387252e..e5eda8559b 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 @@ -240,62 +240,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); @@ -324,6 +273,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 ef813e190b..1b352290e0 100644 --- a/src/libnm-systemd-shared/src/basic/stat-util.c +++ b/src/libnm-systemd-shared/src/basic/stat-util.c @@ -18,10 +18,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" @@ -175,7 +175,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) @@ -301,7 +301,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; @@ -478,8 +478,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 */ @@ -490,76 +490,6 @@ bool statx_mount_same(const struct new_statx *a, const struct new_statx *b) { return a->stx_dev_major == b->stx_dev_major && 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; -} #endif /* NM_IGNORED */ int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { @@ -568,11 +498,14 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(ret); - fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY); - 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)); } #if 0 /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/stat-util.h b/src/libnm-systemd-shared/src/basic/stat-util.h index 17fb520f74..fcde7784b2 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" @@ -52,10 +51,11 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl static inline int inode_same(const char *filea, const char *fileb, int flags) { return inode_same_at(AT_FDCWD, filea, AT_FDCWD, fileb, flags); } +#if 0 /* NM_IGNORED */ static inline int fd_inode_same(int fda, int fdb) { return inode_same_at(fda, NULL, fdb, NULL, AT_EMPTY_PATH); } - +#endif /* NM_IGNORED */ /* The .f_type field of struct statfs is really weird defined on * different archs. Let's give its type a name. */ typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t; @@ -90,31 +90,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 - #if 0 /* NM_IGNORED */ 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 }); @@ -143,7 +122,4 @@ 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; -} #endif /* NM_IGNORED */ diff --git a/src/libnm-systemd-shared/src/basic/stdio-util.h b/src/libnm-systemd-shared/src/basic/stdio-util.h index c35e665504..6914d9982e 100644 --- a/src/libnm-systemd-shared/src/basic/stdio-util.h +++ b/src/libnm-systemd-shared/src/basic/stdio-util.h @@ -8,6 +8,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 6a06594a67..5efd11eabc 100644 --- a/src/libnm-systemd-shared/src/basic/string-util.c +++ b/src/libnm-systemd-shared/src/basic/string-util.c @@ -16,6 +16,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" @@ -48,35 +49,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; } #if 0 /* NM_IGNORED */ @@ -245,7 +253,7 @@ bool string_has_cc(const char *p, const char *ok) { #if 0 /* NM_IGNORED */ 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; @@ -333,10 +341,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; } @@ -983,33 +987,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; @@ -1074,6 +1051,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; @@ -1502,3 +1488,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 3846669630..f433afc7ac 100644 --- a/src/libnm-systemd-shared/src/basic/strv.c +++ b/src/libnm-systemd-shared/src/basic/strv.c @@ -14,6 +14,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" @@ -637,7 +638,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; @@ -868,11 +869,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; @@ -892,6 +893,7 @@ void strv_print_full(char * const *l, const char *prefix) { STRV_FOREACH(s, l) printf("%s%s\n", strempty(prefix), *s); } +#endif /* NM_IGNORED */ int strv_extendf(char ***l, const char *format, ...) { va_list ap; @@ -907,7 +909,7 @@ int strv_extendf(char ***l, const char *format, ...) { return strv_consume(l, x); } -#endif /* NM_IGNORED */ + char* startswith_strv(const char *s, char * const *l) { STRV_FOREACH(i, l) { @@ -986,14 +988,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; } @@ -1074,7 +1078,22 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) { } #if 0 /* NM_IGNORED */ -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; @@ -1126,7 +1145,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; @@ -1140,7 +1159,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; @@ -1234,4 +1253,25 @@ 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); +} #endif /* NM_IGNORED */ 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 594d531511..06e27cfb39 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.c +++ b/src/libnm-systemd-shared/src/basic/time-util.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "alloc-util.h" @@ -19,8 +20,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" @@ -1643,7 +1642,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 3d1b8d0447..4e1fe711e0 100644 --- a/src/libnm-systemd-shared/src/basic/tmpfile-util.c +++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.c @@ -9,6 +9,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..9b80750a10 --- /dev/null +++ b/src/libnm-systemd-shared/src/basic/user-util.c @@ -0,0 +1,1243 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "nm-sd-adapt-shared.h" + +#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; +} + +#if 0 /* NM_IGNORED */ +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; + } +} +#endif /* NM_IGNORED */ \ No newline at end of file diff --git a/src/libnm-systemd-shared/src/basic/utf8.c b/src/libnm-systemd-shared/src/basic/utf8.c index 33fdd96e9c..74d60f5668 100644 --- a/src/libnm-systemd-shared/src/basic/utf8.c +++ b/src/libnm-systemd-shared/src/basic/utf8.c @@ -616,4 +616,29 @@ 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; + } +} #endif /* NM_IGNORED */ 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..e207958b4a --- /dev/null +++ b/src/libnm-systemd-shared/src/fundamental/assert-fundamental.h @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#if !SD_BOOT +# include +#endif + +#include "macro-fundamental.h" +#include "log.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 + +#if 0 /* NM_IGNORED */ +_noreturn_ void log_assert_failed(const char *text, const char *file, int line, const char *func); +#else /* NM_IGNORED */ +#define log_assert_failed(text, file, line, func) \ + G_STMT_START \ + { \ + log_internal(LOG_CRIT, \ + 0, \ + file, \ + line, \ + func, \ + "Assertion '%s' failed at %s:%u, function %s(). Aborting.", \ + text, \ + file, \ + line, \ + func); \ + g_assert_not_reached(); \ + } \ + G_STMT_END +#endif /* NM_IGNORED */ + +#if 0 /* NM_IGNORED */ +_noreturn_ void log_assert_failed_unreachable(const char *file, int line, const char *func); +#else /* NM_IGNORED */ +#define log_assert_failed_unreachable(file, line, func) \ + G_STMT_START \ + { \ + log_internal(LOG_CRIT, \ + 0, \ + file, \ + line, \ + func, \ + "Code should not be reached at %s:%u, function %s(). Aborting.", \ + file, \ + line, \ + func); \ + g_assert_not_reached(); \ + } \ + G_STMT_END +#endif /* NM_IGNORED */ + +#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 418ea55f1d..abdfb9b6bd 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"); \ @@ -53,6 +56,9 @@ _Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"") #if 0 /* NM_IGNORED */ +#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 \ @@ -123,9 +129,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 @@ -141,45 +157,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__ @@ -434,7 +412,7 @@ _found = true; \ break; \ default: \ - break; \ + ; \ } \ _found; \ }) @@ -467,82 +445,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) \ @@ -550,41 +452,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) @@ -633,3 +505,10 @@ 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))) + +#if 0 /* NM_IGNORED */ +assert_cc(STRLEN(__FILE__) > STRLEN(RELATIVE_SOURCE_PATH) + 1); +#define PROJECT_FILE (&__FILE__[STRLEN(RELATIVE_SOURCE_PATH) + 1]) +#else /* NM_IGNORED */ +#define PROJECT_FILE __FILE__ +#endif /* NM_IGNORED */ \ No newline at end of file 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 6682bc87a3..8e84253564 100644 --- a/src/libnm-systemd-shared/src/fundamental/sha256-fundamental.c +++ b/src/libnm-systemd-shared/src/fundamental/sha256-fundamental.c @@ -23,6 +23,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 da810cb749..0a62dbb22f 100644 --- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c +++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c @@ -85,7 +85,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 42efd04a3e..eaca07159b 100644 --- a/src/libnm-systemd-shared/src/shared/dns-domain.c +++ b/src/libnm-systemd-shared/src/shared/dns-domain.c @@ -15,6 +15,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" @@ -1395,7 +1396,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; @@ -1410,8 +1411,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; }