summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/basic/bitmap.c17
-rw-r--r--src/basic/bitmap.h5
-rw-r--r--src/basic/calendarspec.c131
-rw-r--r--src/basic/calendarspec.h1
-rw-r--r--src/basic/cgroup-util.c83
-rw-r--r--src/basic/cgroup-util.h20
-rw-r--r--src/basic/copy.c2
-rw-r--r--src/basic/exit-status.c18
-rw-r--r--src/basic/exit-status.h16
-rw-r--r--src/basic/fd-util.c6
-rw-r--r--src/basic/fd-util.h1
-rw-r--r--src/basic/fileio.c46
-rw-r--r--src/basic/fileio.h2
-rw-r--r--src/basic/formats-util.h16
-rw-r--r--src/basic/fs-util.c29
-rw-r--r--src/basic/fs-util.h2
-rw-r--r--src/basic/hashmap.c25
-rw-r--r--src/basic/in-addr-util.c105
-rw-r--r--src/basic/in-addr-util.h5
-rw-r--r--src/basic/log.c4
-rw-r--r--src/basic/macro.h9
-rw-r--r--src/basic/missing.h19
-rw-r--r--src/basic/missing_syscall.h2
-rw-r--r--src/basic/mount-util.c50
-rw-r--r--src/basic/mount-util.h2
-rw-r--r--src/basic/nss-util.h2
-rw-r--r--src/basic/parse-util.c26
-rw-r--r--src/basic/parse-util.h3
-rw-r--r--src/basic/proc-cmdline.c18
-rw-r--r--src/basic/process-util.c117
-rw-r--r--src/basic/random-util.c2
-rw-r--r--src/basic/selinux-util.c24
-rw-r--r--src/basic/set.h2
-rw-r--r--src/basic/socket-util.c38
-rw-r--r--src/basic/socket-util.h2
-rw-r--r--src/basic/string-table.h2
-rw-r--r--src/basic/string-util.c45
-rw-r--r--src/basic/string-util.h3
-rw-r--r--src/basic/strv.c52
-rw-r--r--src/basic/strv.h4
-rw-r--r--src/basic/terminal-util.c6
-rw-r--r--src/basic/time-util.c218
-rw-r--r--src/basic/time-util.h31
-rw-r--r--src/basic/unaligned.h18
-rw-r--r--src/basic/unit-name.h1
-rw-r--r--src/basic/user-util.c95
-rw-r--r--src/basic/user-util.h5
-rw-r--r--src/basic/util.c176
-rw-r--r--src/basic/util.h11
-rw-r--r--src/basic/virt.c5
-rw-r--r--src/basic/virt.h1
-rw-r--r--src/boot/bootctl.c462
-rw-r--r--src/boot/efi/console.c8
-rw-r--r--src/cgtop/cgtop.c8
-rw-r--r--src/core/automount.c6
-rw-r--r--src/core/busname.c7
-rw-r--r--src/core/cgroup.c34
-rw-r--r--src/core/cgroup.h1
-rw-r--r--src/core/dbus-cgroup.c85
-rw-r--r--src/core/dbus-execute.c41
-rw-r--r--src/core/dbus-manager.c61
-rw-r--r--src/core/dbus-scope.c2
-rw-r--r--src/core/dbus-socket.c1
-rw-r--r--src/core/dynamic-user.c763
-rw-r--r--src/core/dynamic-user.h66
-rw-r--r--src/core/execute.c455
-rw-r--r--src/core/execute.h23
-rw-r--r--src/core/killall.c7
-rw-r--r--src/core/kmod-setup.c3
-rw-r--r--src/core/load-fragment-gperf.gperf.m423
-rw-r--r--src/core/load-fragment.c197
-rw-r--r--src/core/load-fragment.h2
-rw-r--r--src/core/machine-id-setup.c226
-rw-r--r--src/core/machine-id-setup.h2
-rw-r--r--src/core/macros.systemd.in8
-rw-r--r--src/core/main.c87
-rw-r--r--src/core/manager.c92
-rw-r--r--src/core/manager.h3
-rw-r--r--src/core/mount-setup.c16
-rw-r--r--src/core/mount.c16
-rw-r--r--src/core/mount.h3
-rw-r--r--src/core/namespace.c51
-rw-r--r--src/core/namespace.h6
-rw-r--r--src/core/scope.c21
-rw-r--r--src/core/scope.h3
-rw-r--r--src/core/selinux-access.c2
-rw-r--r--src/core/selinux-setup.c2
-rw-r--r--src/core/service.c97
-rw-r--r--src/core/service.h4
-rw-r--r--src/core/shutdown.c20
-rw-r--r--src/core/socket.c216
-rw-r--r--src/core/socket.h18
-rw-r--r--src/core/swap.c15
-rw-r--r--src/core/swap.h1
-rw-r--r--src/core/system.conf2
-rw-r--r--src/core/transaction.c7
-rw-r--r--src/core/unit.c172
-rw-r--r--src/core/unit.h9
-rw-r--r--src/coredump/coredump.c177
-rw-r--r--src/dbus1-generator/dbus1-generator.c331
-rw-r--r--src/firstboot/firstboot.c2
-rw-r--r--src/fstab-generator/fstab-generator.c78
-rw-r--r--src/gpt-auto-generator/gpt-auto-generator.c134
-rw-r--r--src/hwdb/hwdb.c8
-rw-r--r--src/import/import-common.c8
-rw-r--r--src/import/import.c4
-rw-r--r--src/import/importd.c4
-rw-r--r--src/import/pull-common.c4
-rw-r--r--src/import/pull.c4
-rw-r--r--src/journal-remote/journal-remote.c2
-rw-r--r--src/journal-remote/microhttpd-util.c2
-rw-r--r--src/journal/.gitignore1
-rw-r--r--src/journal/journal-send.c16
-rw-r--r--src/journal/journal-verify.c15
-rw-r--r--src/journal/journalctl.c54
-rw-r--r--src/journal/journald-server.c6
-rw-r--r--src/journal/journald-server.h2
-rw-r--r--src/journal/mmap-cache.c4
-rw-r--r--src/journal/sd-journal.c2
-rw-r--r--src/kernel-install/90-loaderentry.install3
-rw-r--r--src/kernel-install/kernel-install15
-rw-r--r--src/libsystemd-network/arp-util.c10
-rw-r--r--src/libsystemd-network/dhcp-network.c4
-rw-r--r--src/libsystemd-network/icmp6-util.c7
-rw-r--r--src/libsystemd-network/lldp-internal.h2
-rw-r--r--src/libsystemd-network/lldp-neighbor.c51
-rw-r--r--src/libsystemd-network/lldp-neighbor.h8
-rw-r--r--src/libsystemd-network/lldp-network.c3
-rw-r--r--src/libsystemd-network/ndisc-internal.h49
-rw-r--r--src/libsystemd-network/ndisc-router.c779
-rw-r--r--src/libsystemd-network/ndisc-router.h62
-rw-r--r--src/libsystemd-network/network-internal.c23
-rw-r--r--src/libsystemd-network/sd-dhcp-server.c2
-rw-r--r--src/libsystemd-network/sd-lldp.c105
-rw-r--r--src/libsystemd-network/sd-ndisc.c551
-rw-r--r--src/libsystemd-network/test-lldp.c8
-rw-r--r--src/libsystemd-network/test-ndisc-rs.c163
-rw-r--r--src/libsystemd/libsystemd.sym5
-rw-r--r--src/libsystemd/sd-bus/bus-common-errors.c1
-rw-r--r--src/libsystemd/sd-bus/bus-common-errors.h1
-rw-r--r--src/libsystemd/sd-bus/bus-match.c3
-rw-r--r--src/libsystemd/sd-bus/bus-message.c4
-rw-r--r--src/libsystemd/sd-bus/bus-socket.c2
-rw-r--r--src/libsystemd/sd-bus/busctl.c3
-rw-r--r--src/libsystemd/sd-daemon/sd-daemon.c4
-rw-r--r--src/libsystemd/sd-device/device-enumerator.c34
-rw-r--r--src/libsystemd/sd-device/device-internal.h2
-rw-r--r--src/libsystemd/sd-device/sd-device.c129
-rw-r--r--src/libsystemd/sd-event/sd-event.c70
-rw-r--r--src/libsystemd/sd-id128/id128-util.c194
-rw-r--r--src/libsystemd/sd-id128/id128-util.h45
-rw-r--r--src/libsystemd/sd-id128/sd-id128.c118
-rw-r--r--src/libsystemd/sd-login/sd-login.c2
-rw-r--r--src/libsystemd/sd-netlink/netlink-message.c10
-rw-r--r--src/libsystemd/sd-netlink/netlink-types.c8
-rw-r--r--src/libsystemd/sd-netlink/netlink-types.h1
-rw-r--r--src/libsystemd/sd-netlink/sd-netlink.c5
-rw-r--r--src/libsystemd/sd-resolve/sd-resolve.c4
-rw-r--r--src/libudev/libudev-device.c2
-rw-r--r--src/libudev/libudev-monitor.c14
-rw-r--r--src/locale/keymap-util.c724
-rw-r--r--src/locale/keymap-util.h46
-rw-r--r--src/locale/localectl.c3
-rw-r--r--src/locale/localed.c818
-rw-r--r--src/locale/test-keymap-util.c220
-rw-r--r--src/login/loginctl.c8
-rw-r--r--src/login/logind-action.c4
-rw-r--r--src/login/logind-core.c2
-rw-r--r--src/login/logind-gperf.gperf2
-rw-r--r--src/login/logind-session.c26
-rw-r--r--src/login/logind-user.c75
-rw-r--r--src/login/logind.c4
-rw-r--r--src/login/logind.conf.in2
-rw-r--r--src/login/logind.h1
-rw-r--r--src/login/pam_systemd.c31
-rw-r--r--src/machine-id-setup/machine-id-setup-main.c30
-rw-r--r--src/machine/image-dbus.c4
-rw-r--r--src/machine/machine-dbus.c4
-rw-r--r--src/machine/machine.c2
-rw-r--r--src/machine/machinectl.c171
-rw-r--r--src/machine/machined-dbus.c214
-rw-r--r--src/machine/machined.c2
-rw-r--r--src/machine/operation.c43
-rw-r--r--src/machine/operation.h4
-rw-r--r--src/machine/org.freedesktop.machine1.conf4
-rw-r--r--src/network/networkd-address.c72
-rw-r--r--src/network/networkd-address.h2
-rw-r--r--src/network/networkd-brvlan.c329
-rw-r--r--src/network/networkd-brvlan.h29
-rw-r--r--src/network/networkd-conf.c2
-rw-r--r--src/network/networkd-dhcp6.c15
-rw-r--r--src/network/networkd-fdb.c34
-rw-r--r--src/network/networkd-fdb.h4
-rw-r--r--src/network/networkd-ipv4ll.c2
-rw-r--r--src/network/networkd-link.c243
-rw-r--r--src/network/networkd-link.h8
-rw-r--r--src/network/networkd-ndisc.c530
-rw-r--r--src/network/networkd-ndisc.h39
-rw-r--r--src/network/networkd-netdev-gperf.gperf10
-rw-r--r--src/network/networkd-netdev-tunnel.c79
-rw-r--r--src/network/networkd-netdev-tunnel.h9
-rw-r--r--src/network/networkd-netdev-vlan.c15
-rw-r--r--src/network/networkd-netdev-vlan.h4
-rw-r--r--src/network/networkd-netdev-vrf.c50
-rw-r--r--src/network/networkd-netdev-vrf.h33
-rw-r--r--src/network/networkd-netdev.c10
-rw-r--r--src/network/networkd-netdev.h1
-rw-r--r--src/network/networkd-network-bus.c2
-rw-r--r--src/network/networkd-network-gperf.gperf12
-rw-r--r--src/network/networkd-network.c16
-rw-r--r--src/network/networkd-network.h17
-rw-r--r--src/network/networkd-route.c126
-rw-r--r--src/network/networkd-route.h8
-rw-r--r--src/network/networkd-wait-online-manager.c8
-rw-r--r--src/network/networkd.h1
-rw-r--r--src/nspawn/nspawn-cgroup.c2
-rw-r--r--src/nspawn/nspawn-gperf.gperf1
-rw-r--r--src/nspawn/nspawn-mount.c216
-rw-r--r--src/nspawn/nspawn-mount.h2
-rw-r--r--src/nspawn/nspawn-network.c2
-rw-r--r--src/nspawn/nspawn-patch-uid.c9
-rw-r--r--src/nspawn/nspawn-register.c11
-rw-r--r--src/nspawn/nspawn-seccomp.c84
-rw-r--r--src/nspawn/nspawn-settings.h4
-rw-r--r--src/nspawn/nspawn-setuid.c7
-rw-r--r--src/nspawn/nspawn.c451
-rw-r--r--src/nss-myhostname/nss-myhostname.c12
l---------src/nss-systemd/Makefile1
-rw-r--r--src/nss-systemd/nss-systemd.c332
-rw-r--r--src/nss-systemd/nss-systemd.sym17
-rw-r--r--src/resolve/dns-type.c9
-rw-r--r--src/resolve/dns-type.h1
-rw-r--r--src/resolve/resolv.conf11
-rw-r--r--src/resolve/resolve-tool.c591
-rw-r--r--src/resolve/resolved-bus.c84
-rw-r--r--src/resolve/resolved-conf.c16
-rw-r--r--src/resolve/resolved-dns-answer.c6
-rw-r--r--src/resolve/resolved-dns-answer.h6
-rw-r--r--src/resolve/resolved-dns-cache.c32
-rw-r--r--src/resolve/resolved-dns-cache.h2
-rw-r--r--src/resolve/resolved-dns-dnssec.c2
-rw-r--r--src/resolve/resolved-dns-packet.c83
-rw-r--r--src/resolve/resolved-dns-packet.h38
-rw-r--r--src/resolve/resolved-dns-query.c16
-rw-r--r--src/resolve/resolved-dns-query.h8
-rw-r--r--src/resolve/resolved-dns-question.h4
-rw-r--r--src/resolve/resolved-dns-rr.c245
-rw-r--r--src/resolve/resolved-dns-rr.h11
-rw-r--r--src/resolve/resolved-dns-scope.c127
-rw-r--r--src/resolve/resolved-dns-scope.h2
-rw-r--r--src/resolve/resolved-dns-server.c132
-rw-r--r--src/resolve/resolved-dns-server.h12
-rw-r--r--src/resolve/resolved-dns-stream.c29
-rw-r--r--src/resolve/resolved-dns-stream.h23
-rw-r--r--src/resolve/resolved-dns-stub.c572
-rw-r--r--src/resolve/resolved-dns-stub.h31
-rw-r--r--src/resolve/resolved-dns-transaction.c125
-rw-r--r--src/resolve/resolved-dns-transaction.h5
-rw-r--r--src/resolve/resolved-dns-zone.c13
-rw-r--r--src/resolve/resolved-dns-zone.h2
-rw-r--r--src/resolve/resolved-gperf.gperf1
-rw-r--r--src/resolve/resolved-link-bus.c87
-rw-r--r--src/resolve/resolved-link.c329
-rw-r--r--src/resolve/resolved-link.h11
-rw-r--r--src/resolve/resolved-llmnr.c33
-rw-r--r--src/resolve/resolved-manager.c176
-rw-r--r--src/resolve/resolved-manager.h18
-rw-r--r--src/resolve/resolved-resolv-conf.c38
-rw-r--r--src/resolve/resolved.c22
-rw-r--r--src/resolve/resolved.conf.in1
-rw-r--r--src/resolve/test-dns-packet.c17
-rw-r--r--src/run/run.c2
-rw-r--r--src/shared/ask-password-api.c6
-rw-r--r--src/shared/bus-unit-util.c110
-rw-r--r--src/shared/bus-util.c49
-rw-r--r--src/shared/bus-util.h3
-rw-r--r--src/shared/condition.c7
-rw-r--r--src/shared/conf-parser.c9
-rw-r--r--src/shared/fdset.c (renamed from src/basic/fdset.c)0
-rw-r--r--src/shared/fdset.h (renamed from src/basic/fdset.h)0
-rw-r--r--src/shared/install.c12
-rw-r--r--src/shared/logs-show.c187
-rw-r--r--src/shared/output-mode.c1
-rw-r--r--src/shared/output-mode.h1
-rw-r--r--src/shared/path-lookup.c2
-rw-r--r--src/shared/seccomp-util.c55
-rw-r--r--src/shared/vlan-util.c69
-rw-r--r--src/shared/vlan-util.h35
-rw-r--r--src/systemctl/systemctl.c780
-rw-r--r--src/systemd/sd-daemon.h5
-rw-r--r--src/systemd/sd-event.h1
-rw-r--r--src/systemd/sd-lldp.h15
-rw-r--r--src/systemd/sd-ndisc.h114
-rw-r--r--src/systemd/sd-netlink.h2
-rw-r--r--src/sysusers/sysusers.c77
-rw-r--r--src/sysv-generator/sysv-generator.c27
-rw-r--r--src/test/test-calendarspec.c35
-rw-r--r--src/test/test-cgroup.c10
-rw-r--r--src/test/test-condition.c18
-rw-r--r--src/test/test-execute.c15
-rw-r--r--src/test/test-fs-util.c46
-rw-r--r--src/test/test-id128.c81
-rw-r--r--src/test/test-install-root.c7
-rw-r--r--src/test/test-nss.c4
-rw-r--r--src/test/test-parse-util.c25
-rw-r--r--src/test/test-path-util.c3
-rw-r--r--src/test/test-proc-cmdline.c11
-rw-r--r--src/test/test-process-util.c283
-rw-r--r--src/test/test-socket-util.c59
-rw-r--r--src/test/test-strv.c17
-rw-r--r--src/test/test-time.c44
-rw-r--r--src/test/test-unaligned.c24
-rw-r--r--src/test/test-user-util.c87
-rw-r--r--src/test/test-util.c88
-rw-r--r--src/timedate/timedatectl.c11
-rw-r--r--src/timedate/timedated.c2
-rw-r--r--src/tmpfiles/tmpfiles.c7
-rw-r--r--src/udev/udev-builtin-blkid.c10
-rw-r--r--src/udev/udev-builtin-input_id.c10
-rw-r--r--src/udev/udev-event.c2
-rw-r--r--src/udev/udevadm-hwdb.c8
-rw-r--r--src/udev/udevadm-info.c12
-rw-r--r--src/udev/udevadm-monitor.c3
-rw-r--r--src/udev/udevd.c2
-rw-r--r--src/vconsole/vconsole-setup.c259
325 files changed, 14630 insertions, 4639 deletions
diff --git a/src/basic/bitmap.c b/src/basic/bitmap.c
index ad1fda0198..f4b12fc261 100644
--- a/src/basic/bitmap.c
+++ b/src/basic/bitmap.c
@@ -50,6 +50,23 @@ Bitmap *bitmap_new(void) {
return new0(Bitmap, 1);
}
+Bitmap *bitmap_copy(Bitmap *b) {
+ Bitmap *ret;
+
+ ret = bitmap_new();
+ if (!ret)
+ return NULL;
+
+ ret->bitmaps = newdup(uint64_t, b->bitmaps, b->n_bitmaps);
+ if (!ret->bitmaps) {
+ free(ret);
+ return NULL;
+ }
+
+ ret->n_bitmaps = ret->bitmaps_allocated = b->n_bitmaps;
+ return ret;
+}
+
void bitmap_free(Bitmap *b) {
if (!b)
return;
diff --git a/src/basic/bitmap.h b/src/basic/bitmap.h
index f5f8f2f018..63fdbe8bea 100644
--- a/src/basic/bitmap.h
+++ b/src/basic/bitmap.h
@@ -27,10 +27,9 @@
typedef struct Bitmap Bitmap;
Bitmap *bitmap_new(void);
-
-void bitmap_free(Bitmap *b);
-
+Bitmap *bitmap_copy(Bitmap *b);
int bitmap_ensure_allocated(Bitmap **b);
+void bitmap_free(Bitmap *b);
int bitmap_set(Bitmap *b, unsigned n);
void bitmap_unset(Bitmap *b, unsigned n);
diff --git a/src/basic/calendarspec.c b/src/basic/calendarspec.c
index 6e0bab9b94..fda293fcb9 100644
--- a/src/basic/calendarspec.c
+++ b/src/basic/calendarspec.c
@@ -32,6 +32,8 @@
#include "parse-util.h"
#include "string-util.h"
+/* Longest valid date/time range is 1970..2199 */
+#define MAX_RANGE_LEN 230
#define BITS_WEEKDAYS 127
static void free_chain(CalendarComponent *c) {
@@ -200,7 +202,7 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) {
};
int l, x;
- bool need_colon = false;
+ bool need_comma = false;
assert(f);
assert(c);
@@ -211,10 +213,10 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) {
if (c->weekdays_bits & (1 << x)) {
if (l < 0) {
- if (need_colon)
+ if (need_comma)
fputc(',', f);
else
- need_colon = true;
+ need_comma = true;
fputs(days[x], f);
l = x;
@@ -223,7 +225,7 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) {
} else if (l >= 0) {
if (x > l + 1) {
- fputc(x > l + 2 ? '-' : ',', f);
+ fputs(x > l + 2 ? ".." : ",", f);
fputs(days[x-1], f);
}
@@ -232,7 +234,7 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) {
}
if (l >= 0 && x > l + 1) {
- fputc(x > l + 2 ? '-' : ',', f);
+ fputs(x > l + 2 ? ".." : ",", f);
fputs(days[x-1], f);
}
}
@@ -300,6 +302,17 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) {
if (c->utc)
fputs(" UTC", f);
+ else if (IN_SET(c->dst, 0, 1)) {
+
+ /* If daylight saving is explicitly on or off, let's show the used timezone. */
+
+ tzset();
+
+ if (!isempty(tzname[c->dst])) {
+ fputc(' ', f);
+ fputs(tzname[c->dst], f);
+ }
+ }
r = fflush_and_check(f);
if (r < 0) {
@@ -357,6 +370,7 @@ static int parse_weekdays(const char **p, CalendarSpec *c) {
skip = strlen(day_nr[i].name);
if ((*p)[skip] != '-' &&
+ (*p)[skip] != '.' &&
(*p)[skip] != ',' &&
(*p)[skip] != ' ' &&
(*p)[skip] != 0)
@@ -394,7 +408,18 @@ static int parse_weekdays(const char **p, CalendarSpec *c) {
return 0;
}
- if (**p == '-') {
+ if (**p == '.') {
+ if (l >= 0)
+ return -EINVAL;
+
+ if ((*p)[1] != '.')
+ return -EINVAL;
+
+ l = day_nr[i].nr;
+ *p += 1;
+
+ /* Support ranges with "-" for backwards compatibility */
+ } else if (**p == '-') {
if (l >= 0)
return -EINVAL;
@@ -448,8 +473,26 @@ static int parse_component_decimal(const char **p, bool usec, unsigned long *res
return 0;
}
+static int const_chain(int value, CalendarComponent **c) {
+ CalendarComponent *cc = NULL;
+
+ assert(c);
+
+ cc = new0(CalendarComponent, 1);
+ if (!cc)
+ return -ENOMEM;
+
+ cc->value = value;
+ cc->repeat = 0;
+ cc->next = *c;
+
+ *c = cc;
+
+ return 0;
+}
+
static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
- unsigned long value, repeat = 0;
+ unsigned long i, value, range_end, range_inc, repeat = 0;
CalendarComponent *cc;
int r;
const char *e;
@@ -471,6 +514,30 @@ static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
if (repeat == 0)
return -ERANGE;
+ } else if (e[0] == '.' && e[1] == '.') {
+ e += 2;
+ r = parse_component_decimal(&e, usec, &range_end);
+ if (r < 0)
+ return r;
+
+ if (value >= range_end)
+ return -EINVAL;
+
+ range_inc = usec ? USEC_PER_SEC : 1;
+
+ /* Don't allow impossibly large ranges... */
+ if (range_end - value >= MAX_RANGE_LEN * range_inc)
+ return -EINVAL;
+
+ /* ...or ranges with only a single element */
+ if (range_end - value < range_inc)
+ return -EINVAL;
+
+ for (i = value; i <= range_end; i += range_inc) {
+ r = const_chain(i, c);
+ if (r < 0)
+ return r;
+ }
}
if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
@@ -495,24 +562,6 @@ static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
return 0;
}
-static int const_chain(int value, CalendarComponent **c) {
- CalendarComponent *cc = NULL;
-
- assert(c);
-
- cc = new0(CalendarComponent, 1);
- if (!cc)
- return -ENOMEM;
-
- cc->value = value;
- cc->repeat = 0;
- cc->next = *c;
-
- *c = cc;
-
- return 0;
-}
-
static int parse_chain(const char **p, bool usec, CalendarComponent **c) {
const char *t;
CalendarComponent *cc = NULL;
@@ -709,9 +758,9 @@ fail:
}
int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
+ const char *utc;
CalendarSpec *c;
int r;
- const char *utc;
assert(p);
assert(spec);
@@ -722,11 +771,39 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
c = new0(CalendarSpec, 1);
if (!c)
return -ENOMEM;
+ c->dst = -1;
utc = endswith_no_case(p, " UTC");
if (utc) {
c->utc = true;
p = strndupa(p, utc - p);
+ } else {
+ const char *e = NULL;
+ int j;
+
+ tzset();
+
+ /* Check if the local timezone was specified? */
+ for (j = 0; j <= 1; j++) {
+ if (isempty(tzname[j]))
+ continue;
+
+ e = endswith_no_case(p, tzname[j]);
+ if(!e)
+ continue;
+ if (e == p)
+ continue;
+ if (e[-1] != ' ')
+ continue;
+
+ break;
+ }
+
+ /* Found one of the two timezones specified? */
+ if (IN_SET(j, 0, 1)) {
+ p = strndupa(p, e - p - 1);
+ c->dst = j;
+ }
}
if (strcaseeq(p, "minutely")) {
@@ -979,7 +1056,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
for (;;) {
/* Normalize the current date */
(void) mktime_or_timegm(&c, spec->utc);
- c.tm_isdst = -1;
+ c.tm_isdst = spec->dst;
c.tm_year += 1900;
r = find_matching_component(spec->year, &c.tm_year);
diff --git a/src/basic/calendarspec.h b/src/basic/calendarspec.h
index f6472c1244..c6087228fd 100644
--- a/src/basic/calendarspec.h
+++ b/src/basic/calendarspec.h
@@ -37,6 +37,7 @@ typedef struct CalendarComponent {
typedef struct CalendarSpec {
int weekdays_bits;
bool utc;
+ int dst;
CalendarComponent *year;
CalendarComponent *month;
diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c
index 7cdc97ee3c..302b958d0d 100644
--- a/src/basic/cgroup-util.c
+++ b/src/basic/cgroup-util.c
@@ -134,6 +134,20 @@ int cg_read_event(const char *controller, const char *path, const char *event,
return -ENOENT;
}
+bool cg_ns_supported(void) {
+ static thread_local int enabled = -1;
+
+ if (enabled >= 0)
+ return enabled;
+
+ if (access("/proc/self/ns/cgroup", F_OK) == 0)
+ enabled = 1;
+ else
+ enabled = 0;
+
+ return enabled;
+}
+
int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) {
_cleanup_free_ char *fs = NULL;
int r;
@@ -197,7 +211,15 @@ int cg_rmdir(const char *controller, const char *path) {
return 0;
}
-int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) {
+int cg_kill(
+ const char *controller,
+ const char *path,
+ int sig,
+ CGroupFlags flags,
+ Set *s,
+ cg_kill_log_func_t log_kill,
+ void *userdata) {
+
_cleanup_set_free_ Set *allocated_set = NULL;
bool done = false;
int r, ret = 0;
@@ -205,6 +227,11 @@ int cg_kill(const char *controller, const char *path, int sig, bool sigcont, boo
assert(sig >= 0);
+ /* Don't send SIGCONT twice. Also, SIGKILL always works even when process is suspended, hence don't send
+ * SIGCONT on SIGKILL. */
+ if (IN_SET(sig, SIGCONT, SIGKILL))
+ flags &= ~CGROUP_SIGCONT;
+
/* This goes through the tasks list and kills them all. This
* is repeated until no further processes are added to the
* tasks list, to properly handle forking processes */
@@ -232,19 +259,22 @@ int cg_kill(const char *controller, const char *path, int sig, bool sigcont, boo
while ((r = cg_read_pid(f, &pid)) > 0) {
- if (ignore_self && pid == my_pid)
+ if ((flags & CGROUP_IGNORE_SELF) && pid == my_pid)
continue;
if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid))
continue;
+ if (log_kill)
+ log_kill(pid, sig, userdata);
+
/* If we haven't killed this process yet, kill
* it */
if (kill(pid, sig) < 0) {
if (ret >= 0 && errno != ESRCH)
ret = -errno;
} else {
- if (sigcont && sig != SIGKILL)
+ if (flags & CGROUP_SIGCONT)
(void) kill(pid, SIGCONT);
if (ret == 0)
@@ -278,7 +308,15 @@ int cg_kill(const char *controller, const char *path, int sig, bool sigcont, boo
return ret;
}
-int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) {
+int cg_kill_recursive(
+ const char *controller,
+ const char *path,
+ int sig,
+ CGroupFlags flags,
+ Set *s,
+ cg_kill_log_func_t log_kill,
+ void *userdata) {
+
_cleanup_set_free_ Set *allocated_set = NULL;
_cleanup_closedir_ DIR *d = NULL;
int r, ret;
@@ -293,7 +331,7 @@ int cg_kill_recursive(const char *controller, const char *path, int sig, bool si
return -ENOMEM;
}
- ret = cg_kill(controller, path, sig, sigcont, ignore_self, s);
+ ret = cg_kill(controller, path, sig, flags, s, log_kill, userdata);
r = cg_enumerate_subgroups(controller, path, &d);
if (r < 0) {
@@ -311,15 +349,14 @@ int cg_kill_recursive(const char *controller, const char *path, int sig, bool si
if (!p)
return -ENOMEM;
- r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s);
+ r = cg_kill_recursive(controller, p, sig, flags, s, log_kill, userdata);
if (r != 0 && ret >= 0)
ret = r;
}
-
if (ret >= 0 && r < 0)
ret = r;
- if (rem) {
+ if (flags & CGROUP_REMOVE) {
r = cg_rmdir(controller, path);
if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY)
return r;
@@ -328,7 +365,13 @@ int cg_kill_recursive(const char *controller, const char *path, int sig, bool si
return ret;
}
-int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self) {
+int cg_migrate(
+ const char *cfrom,
+ const char *pfrom,
+ const char *cto,
+ const char *pto,
+ CGroupFlags flags) {
+
bool done = false;
_cleanup_set_free_ Set *s = NULL;
int r, ret = 0;
@@ -363,7 +406,7 @@ int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char
/* This might do weird stuff if we aren't a
* single-threaded program. However, we
* luckily know we are not */
- if (ignore_self && pid == my_pid)
+ if ((flags & CGROUP_IGNORE_SELF) && pid == my_pid)
continue;
if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid))
@@ -411,8 +454,7 @@ int cg_migrate_recursive(
const char *pfrom,
const char *cto,
const char *pto,
- bool ignore_self,
- bool rem) {
+ CGroupFlags flags) {
_cleanup_closedir_ DIR *d = NULL;
int r, ret = 0;
@@ -423,7 +465,7 @@ int cg_migrate_recursive(
assert(cto);
assert(pto);
- ret = cg_migrate(cfrom, pfrom, cto, pto, ignore_self);
+ ret = cg_migrate(cfrom, pfrom, cto, pto, flags);
r = cg_enumerate_subgroups(cfrom, pfrom, &d);
if (r < 0) {
@@ -441,7 +483,7 @@ int cg_migrate_recursive(
if (!p)
return -ENOMEM;
- r = cg_migrate_recursive(cfrom, p, cto, pto, ignore_self, rem);
+ r = cg_migrate_recursive(cfrom, p, cto, pto, flags);
if (r != 0 && ret >= 0)
ret = r;
}
@@ -449,7 +491,7 @@ int cg_migrate_recursive(
if (r < 0 && ret >= 0)
ret = r;
- if (rem) {
+ if (flags & CGROUP_REMOVE) {
r = cg_rmdir(cfrom, pfrom);
if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY)
return r;
@@ -463,8 +505,7 @@ int cg_migrate_recursive_fallback(
const char *pfrom,
const char *cto,
const char *pto,
- bool ignore_self,
- bool rem) {
+ CGroupFlags flags) {
int r;
@@ -473,7 +514,7 @@ int cg_migrate_recursive_fallback(
assert(cto);
assert(pto);
- r = cg_migrate_recursive(cfrom, pfrom, cto, pto, ignore_self, rem);
+ r = cg_migrate_recursive(cfrom, pfrom, cto, pto, flags);
if (r < 0) {
char prefix[strlen(pto) + 1];
@@ -482,7 +523,7 @@ int cg_migrate_recursive_fallback(
PATH_FOREACH_PREFIX(prefix, pto) {
int q;
- q = cg_migrate_recursive(cfrom, pfrom, cto, prefix, ignore_self, rem);
+ q = cg_migrate_recursive(cfrom, pfrom, cto, prefix, flags);
if (q >= 0)
return q;
}
@@ -1955,7 +1996,7 @@ int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to
int r = 0, unified;
if (!path_equal(from, to)) {
- r = cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, from, SYSTEMD_CGROUP_CONTROLLER, to, false, true);
+ r = cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, from, SYSTEMD_CGROUP_CONTROLLER, to, CGROUP_REMOVE);
if (r < 0)
return r;
}
@@ -1979,7 +2020,7 @@ int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to
if (!p)
p = to;
- (void) cg_migrate_recursive_fallback(SYSTEMD_CGROUP_CONTROLLER, to, cgroup_controller_to_string(c), p, false, false);
+ (void) cg_migrate_recursive_fallback(SYSTEMD_CGROUP_CONTROLLER, to, cgroup_controller_to_string(c), p, 0);
}
return 0;
diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h
index 4bb5291296..ec5c715987 100644
--- a/src/basic/cgroup-util.h
+++ b/src/basic/cgroup-util.h
@@ -135,12 +135,20 @@ int cg_read_event(const char *controller, const char *path, const char *event,
int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d);
int cg_read_subgroup(DIR *d, char **fn);
-int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s);
-int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool remove, Set *s);
+typedef enum CGroupFlags {
+ CGROUP_SIGCONT = 1,
+ CGROUP_IGNORE_SELF = 2,
+ CGROUP_REMOVE = 4,
+} CGroupFlags;
-int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self);
-int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool remove);
-int cg_migrate_recursive_fallback(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self, bool rem);
+typedef void (*cg_kill_log_func_t)(pid_t pid, int sig, void *userdata);
+
+int cg_kill(const char *controller, const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata);
+int cg_kill_recursive(const char *controller, const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata);
+
+int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, CGroupFlags flags);
+int cg_migrate_recursive(const char *cfrom, const char *pfrom, const char *cto, const char *pto, CGroupFlags flags);
+int cg_migrate_recursive_fallback(const char *cfrom, const char *pfrom, const char *cto, const char *pto, CGroupFlags flags);
int cg_split_spec(const char *spec, char **controller, char **path);
int cg_mangle_path(const char *path, char **result);
@@ -214,6 +222,8 @@ int cg_mask_supported(CGroupMask *ret);
int cg_kernel_controllers(Set *controllers);
+bool cg_ns_supported(void);
+
int cg_unified(void);
void cg_unified_flush(void);
diff --git a/src/basic/copy.c b/src/basic/copy.c
index c3586728d0..9883f5fa31 100644
--- a/src/basic/copy.c
+++ b/src/basic/copy.c
@@ -169,7 +169,7 @@ int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink) {
/* sendfile accepts at most SSIZE_MAX-offset bytes to copy,
* so reduce our maximum by the amount we already copied,
* but don't go below our copy buffer size, unless we are
- * close the the limit of bytes we are allowed to copy. */
+ * close the limit of bytes we are allowed to copy. */
m = MAX(MIN(COPY_BUFFER_SIZE, max_bytes), m - n);
}
diff --git a/src/basic/exit-status.c b/src/basic/exit-status.c
index 92fa5ace61..d488cfc59f 100644
--- a/src/basic/exit-status.c
+++ b/src/basic/exit-status.c
@@ -38,8 +38,7 @@ const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) {
return "FAILURE";
}
-
- if (level == EXIT_STATUS_SYSTEMD || level == EXIT_STATUS_LSB) {
+ if (IN_SET(level, EXIT_STATUS_SYSTEMD, EXIT_STATUS_LSB)) {
switch ((int) status) {
case EXIT_CHDIR:
@@ -189,13 +188,9 @@ bool is_clean_exit(int code, int status, ExitStatusSet *success_status) {
/* If a daemon does not implement handlers for some of the
* signals that's not considered an unclean shutdown */
if (code == CLD_KILLED)
- return
- status == SIGHUP ||
- status == SIGINT ||
- status == SIGTERM ||
- status == SIGPIPE ||
+ return IN_SET(status, SIGHUP, SIGINT, SIGTERM, SIGPIPE) ||
(success_status &&
- set_contains(success_status->signal, INT_TO_PTR(status)));
+ set_contains(success_status->signal, INT_TO_PTR(status)));
return false;
}
@@ -207,15 +202,14 @@ bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) {
return
code == CLD_EXITED &&
- (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED);
+ IN_SET(status, EXIT_NOTINSTALLED, EXIT_NOTCONFIGURED);
}
void exit_status_set_free(ExitStatusSet *x) {
assert(x);
- set_free(x->status);
- set_free(x->signal);
- x->status = x->signal = NULL;
+ x->status = set_free(x->status);
+ x->signal = set_free(x->signal);
}
bool exit_status_set_is_empty(ExitStatusSet *x) {
diff --git a/src/basic/exit-status.h b/src/basic/exit-status.h
index 1208c8feed..2309f68815 100644
--- a/src/basic/exit-status.h
+++ b/src/basic/exit-status.h
@@ -25,6 +25,12 @@
#include "macro.h"
#include "set.h"
+/* This defines pretty names for the LSB 'start' verb exit codes. Note that they shouldn't be confused with the LSB
+ * 'status' verb exit codes which are defined very differently. For details see:
+ *
+ * https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
+ */
+
typedef enum ExitStatus {
/* EXIT_SUCCESS defined by libc */
/* EXIT_FAILURE defined by libc */
@@ -37,9 +43,7 @@ typedef enum ExitStatus {
/* The LSB suggests that error codes >= 200 are "reserved". We
* use them here under the assumption that they hence are
- * unused by init scripts.
- *
- * http://refspecs.linuxfoundation.org/LSB_3.2.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */
+ * unused by init scripts. */
EXIT_CHDIR = 200,
EXIT_NICE,
@@ -81,9 +85,9 @@ typedef enum ExitStatus {
} ExitStatus;
typedef enum ExitStatusLevel {
- EXIT_STATUS_MINIMAL,
- EXIT_STATUS_SYSTEMD,
- EXIT_STATUS_LSB,
+ EXIT_STATUS_MINIMAL, /* only cover libc EXIT_STATUS/EXIT_FAILURE */
+ EXIT_STATUS_SYSTEMD, /* cover libc and systemd's own exit codes */
+ EXIT_STATUS_LSB, /* cover libc, systemd's own and LSB exit codes */
EXIT_STATUS_FULL = EXIT_STATUS_LSB
} ExitStatusLevel;
diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c
index 8b466cff15..5c820332a5 100644
--- a/src/basic/fd-util.c
+++ b/src/basic/fd-util.c
@@ -186,6 +186,12 @@ int fd_cloexec(int fd, bool cloexec) {
return 0;
}
+void stdio_unset_cloexec(void) {
+ fd_cloexec(STDIN_FILENO, false);
+ fd_cloexec(STDOUT_FILENO, false);
+ fd_cloexec(STDERR_FILENO, false);
+}
+
_pure_ static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) {
unsigned i;
diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h
index b86e41698a..34b98d4aec 100644
--- a/src/basic/fd-util.h
+++ b/src/basic/fd-util.h
@@ -63,6 +63,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DIR*, closedir);
int fd_nonblock(int fd, bool nonblock);
int fd_cloexec(int fd, bool cloexec);
+void stdio_unset_cloexec(void);
int close_all_fds(const int except[], unsigned n_except);
diff --git a/src/basic/fileio.c b/src/basic/fileio.c
index 29f5374222..f183de4999 100644
--- a/src/basic/fileio.c
+++ b/src/basic/fileio.c
@@ -1067,7 +1067,7 @@ int fflush_and_check(FILE *f) {
return 0;
}
-/* This is much like like mkostemp() but is subject to umask(). */
+/* This is much like mkostemp() but is subject to umask(). */
int mkostemp_safe(char *pattern, int flags) {
_cleanup_umask_ mode_t u = 0;
int fd;
@@ -1259,7 +1259,8 @@ int open_tmpfile_unlinkable(const char *directory, int flags) {
char *p;
int fd;
- assert(directory);
+ if (!directory)
+ directory = "/tmp";
/* Returns an unlinked temporary file that cannot be linked into the file system anymore */
@@ -1354,3 +1355,44 @@ int link_tmpfile(int fd, const char *path, const char *target) {
return 0;
}
+
+int read_nul_string(FILE *f, char **ret) {
+ _cleanup_free_ char *x = NULL;
+ size_t allocated = 0, n = 0;
+
+ assert(f);
+ assert(ret);
+
+ /* Reads a NUL-terminated string from the specified file. */
+
+ for (;;) {
+ int c;
+
+ if (!GREEDY_REALLOC(x, allocated, n+2))
+ return -ENOMEM;
+
+ c = fgetc(f);
+ if (c == 0) /* Terminate at NUL byte */
+ break;
+ if (c == EOF) {
+ if (ferror(f))
+ return -errno;
+ break; /* Terminate at EOF */
+ }
+
+ x[n++] = (char) c;
+ }
+
+ if (x)
+ x[n] = 0;
+ else {
+ x = new0(char, 1);
+ if (!x)
+ return -ENOMEM;
+ }
+
+ *ret = x;
+ x = NULL;
+
+ return 0;
+}
diff --git a/src/basic/fileio.h b/src/basic/fileio.h
index 58dbc80c24..9ac497d9eb 100644
--- a/src/basic/fileio.h
+++ b/src/basic/fileio.h
@@ -86,3 +86,5 @@ int open_tmpfile_unlinkable(const char *directory, int flags);
int open_tmpfile_linkable(const char *target, int flags, char **ret_path);
int link_tmpfile(int fd, const char *path, const char *target);
+
+int read_nul_string(FILE *f, char **ret);
diff --git a/src/basic/formats-util.h b/src/basic/formats-util.h
index 9b4e8e98fa..39a185f59b 100644
--- a/src/basic/formats-util.h
+++ b/src/basic/formats-util.h
@@ -61,3 +61,19 @@
#else
# error Unknown rlim_t size
#endif
+
+#if SIZEOF_DEV_T == 8
+# define DEV_FMT "%" PRIu64
+#elif SIZEOF_DEV_T == 4
+# define DEV_FMT "%" PRIu32
+#else
+# error Unknown dev_t size
+#endif
+
+#if SIZEOF_INO_T == 8
+# define INO_FMT "%" PRIu64
+#elif SIZEOF_INO_T == 4
+# define INO_FMT "%" PRIu32
+#else
+# error Unknown ino_t size
+#endif
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index e24e7036f7..f0c6f3265e 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -38,6 +38,7 @@
#include "mkdir.h"
#include "parse-util.h"
#include "path-util.h"
+#include "stat-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
@@ -495,6 +496,34 @@ int get_files_in_directory(const char *path, char ***list) {
return n;
}
+int var_tmp(char **ret) {
+ const char *tmp_dir = NULL;
+ const char *env_tmp_dir = NULL;
+ char *c = NULL;
+ int r;
+
+ assert(ret);
+
+ env_tmp_dir = getenv("TMPDIR");
+ if (env_tmp_dir != NULL) {
+ r = is_dir(env_tmp_dir, true);
+ if (r < 0 && r != -ENOENT)
+ return r;
+ if (r > 0)
+ tmp_dir = env_tmp_dir;
+ }
+
+ if (!tmp_dir)
+ tmp_dir = "/var/tmp";
+
+ c = strdup(tmp_dir);
+ if (!c)
+ return -ENOMEM;
+ *ret = c;
+
+ return 0;
+}
+
int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
char path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
int r;
diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h
index 517b599d6f..075e5942b1 100644
--- a/src/basic/fs-util.h
+++ b/src/basic/fs-util.h
@@ -61,6 +61,8 @@ int mkfifo_atomic(const char *path, mode_t mode);
int get_files_in_directory(const char *path, char ***list);
+int var_tmp(char **ret);
+
#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1)
#define FOREACH_INOTIFY_EVENT(e, buffer, sz) \
diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c
index 49a0479592..50fefb0b54 100644
--- a/src/basic/hashmap.c
+++ b/src/basic/hashmap.c
@@ -1764,6 +1764,9 @@ void *ordered_hashmap_next(OrderedHashmap *h, const void *key) {
int set_consume(Set *s, void *value) {
int r;
+ assert(s);
+ assert(value);
+
r = set_put(s, value);
if (r <= 0)
free(value);
@@ -1791,6 +1794,8 @@ int set_put_strdupv(Set *s, char **l) {
int n = 0, r;
char **i;
+ assert(s);
+
STRV_FOREACH(i, l) {
r = set_put_strdup(s, *i);
if (r < 0)
@@ -1801,3 +1806,23 @@ int set_put_strdupv(Set *s, char **l) {
return n;
}
+
+int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags flags) {
+ const char *p = v;
+ int r;
+
+ assert(s);
+ assert(v);
+
+ for (;;) {
+ char *word;
+
+ r = extract_first_word(&p, &word, separators, flags);
+ if (r <= 0)
+ return r;
+
+ r = set_consume(s, word);
+ if (r < 0)
+ return r;
+ }
+}
diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c
index 245107ebb8..aa7ccd1afd 100644
--- a/src/basic/in-addr-util.c
+++ b/src/basic/in-addr-util.c
@@ -20,26 +20,36 @@
#include <arpa/inet.h>
#include <endian.h>
#include <errno.h>
+#include <net/if.h>
#include <stdint.h>
#include <stdlib.h>
#include "alloc-util.h"
#include "in-addr-util.h"
#include "macro.h"
+#include "parse-util.h"
#include "util.h"
+bool in4_addr_is_null(const struct in_addr *a) {
+ return a->s_addr == 0;
+}
+
+bool in6_addr_is_null(const struct in6_addr *a) {
+ return
+ a->s6_addr32[0] == 0 &&
+ a->s6_addr32[1] == 0 &&
+ a->s6_addr32[2] == 0 &&
+ a->s6_addr32[3] == 0;
+}
+
int in_addr_is_null(int family, const union in_addr_union *u) {
assert(u);
if (family == AF_INET)
- return u->in.s_addr == 0;
+ return in4_addr_is_null(&u->in);
if (family == AF_INET6)
- return
- u->in6.s6_addr32[0] == 0 &&
- u->in6.s6_addr32[1] == 0 &&
- u->in6.s6_addr32[2] == 0 &&
- u->in6.s6_addr32[3] == 0;
+ return in6_addr_is_null(&u->in6);
return -EAFNOSUPPORT;
}
@@ -224,6 +234,48 @@ int in_addr_to_string(int family, const union in_addr_union *u, char **ret) {
return 0;
}
+int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret) {
+ size_t l;
+ char *x;
+ int r;
+
+ assert(u);
+ assert(ret);
+
+ /* Much like in_addr_to_string(), but optionally appends the zone interface index to the address, to properly
+ * handle IPv6 link-local addresses. */
+
+ if (family != AF_INET6)
+ goto fallback;
+ if (ifindex <= 0)
+ goto fallback;
+
+ r = in_addr_is_link_local(family, u);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ goto fallback;
+
+ l = INET6_ADDRSTRLEN + 1 + DECIMAL_STR_MAX(ifindex) + 1;
+ x = new(char, l);
+ if (!x)
+ return -ENOMEM;
+
+ errno = 0;
+ if (!inet_ntop(family, u, x, l)) {
+ free(x);
+ return errno > 0 ? -errno : -EINVAL;
+ }
+
+ sprintf(strchr(x, 0), "%%%i", ifindex);
+ *ret = x;
+
+ return 0;
+
+fallback:
+ return in_addr_to_string(family, u, ret);
+}
+
int in_addr_from_string(int family, const char *s, union in_addr_union *ret) {
assert(s);
@@ -261,6 +313,47 @@ int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *re
return -EINVAL;
}
+int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex) {
+ const char *suffix;
+ int r, ifi = 0;
+
+ assert(s);
+ assert(family);
+ assert(ret);
+
+ /* Similar to in_addr_from_string_auto() but also parses an optionally appended IPv6 zone suffix ("scope id")
+ * if one is found. */
+
+ suffix = strchr(s, '%');
+ if (suffix) {
+
+ if (ifindex) {
+ /* If we shall return the interface index, try to parse it */
+ r = parse_ifindex(suffix + 1, &ifi);
+ if (r < 0) {
+ unsigned u;
+
+ u = if_nametoindex(suffix + 1);
+ if (u <= 0)
+ return -errno;
+
+ ifi = (int) u;
+ }
+ }
+
+ s = strndupa(s, suffix - s);
+ }
+
+ r = in_addr_from_string_auto(s, family, ret);
+ if (r < 0)
+ return r;
+
+ if (ifindex)
+ *ifindex = ifi;
+
+ return r;
+}
+
unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr) {
assert(addr);
diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h
index 17798ce816..d60064aef8 100644
--- a/src/basic/in-addr-util.h
+++ b/src/basic/in-addr-util.h
@@ -36,6 +36,9 @@ struct in_addr_data {
union in_addr_union address;
};
+bool in4_addr_is_null(const struct in_addr *a);
+bool in6_addr_is_null(const struct in6_addr *a);
+
int in_addr_is_null(int family, const union in_addr_union *u);
int in_addr_is_link_local(int family, const union in_addr_union *u);
int in_addr_is_localhost(int family, const union in_addr_union *u);
@@ -43,8 +46,10 @@ int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_
int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen);
int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
int in_addr_to_string(int family, const union in_addr_union *u, char **ret);
+int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret);
int in_addr_from_string(int family, const char *s, union in_addr_union *ret);
int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *ret);
+int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex);
unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr);
struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen);
int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen);
diff --git a/src/basic/log.c b/src/basic/log.c
index 3ea643b6e6..49b4598b7c 100644
--- a/src/basic/log.c
+++ b/src/basic/log.c
@@ -334,7 +334,7 @@ static int write_to_console(
const char *object,
const char *buffer) {
- char location[64], prefix[1 + DECIMAL_STR_MAX(int) + 2];
+ char location[256], prefix[1 + DECIMAL_STR_MAX(int) + 2];
struct iovec iovec[6] = {};
unsigned n = 0;
bool highlight;
@@ -350,7 +350,7 @@ static int write_to_console(
highlight = LOG_PRI(level) <= LOG_ERR && show_color;
if (show_location) {
- xsprintf(location, "(%s:%i) ", file, line);
+ snprintf(location, sizeof(location), "(%s:%i) ", file, line);
IOVEC_SET_STRING(iovec[n++], location);
}
diff --git a/src/basic/macro.h b/src/basic/macro.h
index e41aa4260f..6b2aeb933f 100644
--- a/src/basic/macro.h
+++ b/src/basic/macro.h
@@ -89,6 +89,15 @@
#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq))
#define UNIQ __COUNTER__
+/* builtins */
+#if __SIZEOF_INT__ == 4
+#define BUILTIN_FFS_U32(x) __builtin_ffs(x);
+#elif __SIZEOF_LONG__ == 4
+#define BUILTIN_FFS_U32(x) __builtin_ffsl(x);
+#else
+#error "neither int nor long are four bytes long?!?"
+#endif
+
/* Rounds up */
#define ALIGN4(l) (((l) + 3) & ~3)
diff --git a/src/basic/missing.h b/src/basic/missing.h
index 51dafcaca9..f8e096605e 100644
--- a/src/basic/missing.h
+++ b/src/basic/missing.h
@@ -445,6 +445,10 @@ struct btrfs_ioctl_quota_ctl_args {
#define CGROUP2_SUPER_MAGIC 0x63677270
#endif
+#ifndef CLONE_NEWCGROUP
+#define CLONE_NEWCGROUP 0x02000000
+#endif
+
#ifndef TMPFS_MAGIC
#define TMPFS_MAGIC 0x01021994
#endif
@@ -577,6 +581,9 @@ struct btrfs_ioctl_quota_ctl_args {
#define IN6_ADDR_GEN_MODE_EUI64 0
#define IN6_ADDR_GEN_MODE_NONE 1
+#endif
+
+#if !HAVE_DECL_IN6_ADDR_GEN_MODE_STABLE_PRIVACY
#define IN6_ADDR_GEN_MODE_STABLE_PRIVACY 2
#endif
@@ -759,6 +766,14 @@ struct btrfs_ioctl_quota_ctl_args {
#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1)
#endif
+#ifndef BRIDGE_VLAN_INFO_RANGE_BEGIN
+#define BRIDGE_VLAN_INFO_RANGE_BEGIN (1<<3) /* VLAN is start of vlan range */
+#endif
+
+#ifndef BRIDGE_VLAN_INFO_RANGE_END
+#define BRIDGE_VLAN_INFO_RANGE_END (1<<4) /* VLAN is end of vlan range */
+#endif
+
#if !HAVE_DECL_IFLA_BR_VLAN_DEFAULT_PVID
#define IFLA_BR_UNSPEC 0
#define IFLA_BR_FORWARD_DELAY 1
@@ -826,6 +841,10 @@ struct btrfs_ioctl_quota_ctl_args {
#define IFLA_BRPORT_PROXYARP 10
#endif
+#if !HAVE_DECL_IFLA_VRF_TABLE
+#define IFLA_VRF_TABLE 1
+#endif
+
#if !HAVE_DECL_NDA_IFINDEX
#define NDA_UNSPEC 0
#define NDA_DST 1
diff --git a/src/basic/missing_syscall.h b/src/basic/missing_syscall.h
index e102083684..e6fd67cb9d 100644
--- a/src/basic/missing_syscall.h
+++ b/src/basic/missing_syscall.h
@@ -279,6 +279,8 @@ static inline key_serial_t request_key(const char *type, const char *description
# define __NR_copy_file_range 391
# elif defined __aarch64__
# define __NR_copy_file_range 285
+# elif defined __powerpc__
+# define __NR_copy_file_range 379
# else
# warning "__NR_copy_file_range not defined for your architecture"
# endif
diff --git a/src/basic/mount-util.c b/src/basic/mount-util.c
index ba698959b7..28dc778969 100644
--- a/src/basic/mount-util.c
+++ b/src/basic/mount-util.c
@@ -104,7 +104,7 @@ int fd_is_mount_point(int fd, const char *filename, int flags) {
*
* As last fallback we do traditional fstat() based st_dev
* comparisons. This is how things were traditionally done,
- * but unionfs breaks breaks this since it exposes file
+ * but unionfs breaks this since it 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. */
@@ -448,21 +448,21 @@ int bind_remount_recursive(const char *prefix, bool ro) {
if (r < 0)
return r;
- /* Try to reuse the original flag set, but
- * don't care for errors, in case of
- * obstructed mounts */
+ /* Deal with mount points that are obstructed by a
+ * later mount */
+ r = path_is_mount_point(x, 0);
+ if (r == -ENOENT || r == 0)
+ continue;
+ if (r < 0)
+ return r;
+
+ /* Try to reuse the original flag set */
orig_flags = 0;
(void) get_mount_flags(x, &orig_flags);
orig_flags &= ~MS_RDONLY;
- if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) {
-
- /* Deal with mount points that are
- * obstructed by a later mount */
-
- if (errno != ENOENT)
- return -errno;
- }
+ if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0)
+ return -errno;
}
}
@@ -500,6 +500,7 @@ bool fstype_is_network(const char *fstype) {
"gfs2\0"
"glusterfs\0"
"pvfs2\0" /* OrangeFS */
+ "ocfs2\0"
;
const char *x;
@@ -531,3 +532,28 @@ int repeat_unmount(const char *path, int flags) {
done = true;
}
}
+
+const char* mode_to_inaccessible_node(mode_t mode) {
+ /* This function maps a node type to the correspondent inaccessible node type.
+ * Character and block inaccessible devices may not be created (because major=0 and minor=0),
+ * in such case we map character and block devices to the inaccessible node type socket. */
+ switch(mode & S_IFMT) {
+ case S_IFREG:
+ return "/run/systemd/inaccessible/reg";
+ case S_IFDIR:
+ return "/run/systemd/inaccessible/dir";
+ case S_IFCHR:
+ if (access("/run/systemd/inaccessible/chr", F_OK) == 0)
+ return "/run/systemd/inaccessible/chr";
+ return "/run/systemd/inaccessible/sock";
+ case S_IFBLK:
+ if (access("/run/systemd/inaccessible/blk", F_OK) == 0)
+ return "/run/systemd/inaccessible/blk";
+ return "/run/systemd/inaccessible/sock";
+ case S_IFIFO:
+ return "/run/systemd/inaccessible/fifo";
+ case S_IFSOCK:
+ return "/run/systemd/inaccessible/sock";
+ }
+ return NULL;
+}
diff --git a/src/basic/mount-util.h b/src/basic/mount-util.h
index bdb525d6b0..f46989ebb3 100644
--- a/src/basic/mount-util.h
+++ b/src/basic/mount-util.h
@@ -49,4 +49,6 @@ union file_handle_union {
char padding[sizeof(struct file_handle) + MAX_HANDLE_SZ];
};
+const char* mode_to_inaccessible_node(mode_t mode);
+
#define FILE_HANDLE_INIT { .handle.handle_bytes = MAX_HANDLE_SZ }
diff --git a/src/basic/nss-util.h b/src/basic/nss-util.h
index bf7c4854fc..e7844fff96 100644
--- a/src/basic/nss-util.h
+++ b/src/basic/nss-util.h
@@ -137,7 +137,7 @@ enum nss_status _nss_##module##_getpwnam_r( \
struct passwd *pwd, \
char *buffer, size_t buflen, \
int *errnop) _public_; \
-enum nss_status _nss_mymachines_getpwuid_r( \
+enum nss_status _nss_##module##_getpwuid_r( \
uid_t uid, \
struct passwd *pwd, \
char *buffer, size_t buflen, \
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
index 6c11b605a9..11849ade0b 100644
--- a/src/basic/parse-util.c
+++ b/src/basic/parse-util.c
@@ -532,3 +532,29 @@ int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) {
return 0;
}
+
+int parse_percent_unbounded(const char *p) {
+ const char *pc, *n;
+ unsigned v;
+ int r;
+
+ pc = endswith(p, "%");
+ if (!pc)
+ return -EINVAL;
+
+ n = strndupa(p, pc - p);
+ r = safe_atou(n, &v);
+ if (r < 0)
+ return r;
+
+ return (int) v;
+}
+
+int parse_percent(const char *p) {
+ int v = parse_percent_unbounded(p);
+
+ if (v > 100)
+ return -ERANGE;
+
+ return v;
+}
diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h
index 7dc579a159..f0fa5f9752 100644
--- a/src/basic/parse-util.h
+++ b/src/basic/parse-util.h
@@ -105,3 +105,6 @@ static inline int safe_atozu(const char *s, size_t *ret_u) {
int safe_atod(const char *s, double *ret_d);
int parse_fractional_part_u(const char **s, size_t digits, unsigned *res);
+
+int parse_percent_unbounded(const char *p);
+int parse_percent(const char *p);
diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c
index 3505fa9c9a..0430beadaa 100644
--- a/src/basic/proc-cmdline.c
+++ b/src/basic/proc-cmdline.c
@@ -160,17 +160,29 @@ static const char * const rlmap[] = {
"3", SPECIAL_MULTI_USER_TARGET,
"4", SPECIAL_MULTI_USER_TARGET,
"5", SPECIAL_GRAPHICAL_TARGET,
+ NULL
+};
+
+static const char * const rlmap_initrd[] = {
+ "emergency", SPECIAL_EMERGENCY_TARGET,
+ "rescue", SPECIAL_RESCUE_TARGET,
+ NULL
};
const char* runlevel_to_target(const char *word) {
size_t i;
+ const char * const *rlmap_ptr = in_initrd() ? rlmap_initrd
+ : rlmap;
if (!word)
return NULL;
- for (i = 0; i < ELEMENTSOF(rlmap); i += 2)
- if (streq(word, rlmap[i]))
- return rlmap[i+1];
+ if (in_initrd() && (word = startswith(word, "rd.")) == NULL)
+ return NULL;
+
+ for (i = 0; rlmap_ptr[i] != NULL; i += 2)
+ if (streq(word, rlmap_ptr[i]))
+ return rlmap_ptr[i+1];
return NULL;
}
diff --git a/src/basic/process-util.c b/src/basic/process-util.c
index b991e7c6ba..54b644ad56 100644
--- a/src/basic/process-util.c
+++ b/src/basic/process-util.c
@@ -102,6 +102,7 @@ int get_process_comm(pid_t pid, char **name) {
int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) {
_cleanup_fclose_ FILE *f = NULL;
+ bool space = false;
char *r = NULL, *k;
const char *p;
int c;
@@ -109,6 +110,15 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
assert(line);
assert(pid >= 0);
+ /* Retrieves a process' command line. Replaces unprintable characters while doing so by whitespace (coalescing
+ * multiple sequential ones into one). If max_length is != 0 will return a string of the specified size at most
+ * (the trailing NUL byte does count towards the length here!), abbreviated with a "..." ellipsis. If
+ * comm_fallback is true and the process has no command line set (the case for kernel threads), or has a
+ * command line that resolves to the empty string will return the "comm" name of the process instead.
+ *
+ * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and
+ * comm_fallback is false). */
+
p = procfs_file_alloca(pid, "cmdline");
f = fopen(p, "re");
@@ -118,24 +128,44 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
return -errno;
}
- if (max_length == 0) {
+ if (max_length == 1) {
+
+ /* If there's only room for one byte, return the empty string */
+ r = new0(char, 1);
+ if (!r)
+ return -ENOMEM;
+
+ *line = r;
+ return 0;
+
+ } else if (max_length == 0) {
size_t len = 0, allocated = 0;
while ((c = getc(f)) != EOF) {
- if (!GREEDY_REALLOC(r, allocated, len+2)) {
+ if (!GREEDY_REALLOC(r, allocated, len+3)) {
free(r);
return -ENOMEM;
}
- r[len++] = isprint(c) ? c : ' ';
- }
+ if (isprint(c)) {
+ if (space) {
+ r[len++] = ' ';
+ space = false;
+ }
+
+ r[len++] = c;
+ } else if (len > 0)
+ space = true;
+ }
if (len > 0)
- r[len-1] = 0;
+ r[len] = 0;
+ else
+ r = mfree(r);
} else {
- bool space = false;
+ bool dotdotdot = false;
size_t left;
r = new(char, max_length);
@@ -147,28 +177,46 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
while ((c = getc(f)) != EOF) {
if (isprint(c)) {
+
if (space) {
- if (left <= 4)
+ if (left <= 2) {
+ dotdotdot = true;
break;
+ }
*(k++) = ' ';
left--;
space = false;
}
- if (left <= 4)
+ if (left <= 1) {
+ dotdotdot = true;
break;
+ }
*(k++) = (char) c;
left--;
- } else
+ } else if (k > r)
space = true;
}
- if (left <= 4) {
- size_t n = MIN(left-1, 3U);
- memcpy(k, "...", n);
- k[n] = 0;
+ if (dotdotdot) {
+ if (max_length <= 4) {
+ k = r;
+ left = max_length;
+ } else {
+ k = r + max_length - 4;
+ left = 4;
+
+ /* Eat up final spaces */
+ while (k > r && isspace(k[-1])) {
+ k--;
+ left++;
+ }
+ }
+
+ strncpy(k, "...", left-1);
+ k[left-1] = 0;
} else
*k = 0;
}
@@ -187,7 +235,37 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
if (h < 0)
return h;
- r = strjoin("[", t, "]", NULL);
+ if (max_length == 0)
+ r = strjoin("[", t, "]", NULL);
+ else {
+ size_t l;
+
+ l = strlen(t);
+
+ if (l + 3 <= max_length)
+ r = strjoin("[", t, "]", NULL);
+ else if (max_length <= 6) {
+
+ r = new(char, max_length);
+ if (!r)
+ return -ENOMEM;
+
+ memcpy(r, "[...]", max_length-1);
+ r[max_length-1] = 0;
+ } else {
+ char *e;
+
+ t[max_length - 6] = 0;
+
+ /* Chop off final spaces */
+ e = strchr(t, 0);
+ while (e > t && isspace(e[-1]))
+ e--;
+ *e = 0;
+
+ r = strjoin("[", t, "...]", NULL);
+ }
+ }
if (!r)
return -ENOMEM;
}
@@ -317,9 +395,6 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) {
assert(field);
assert(uid);
- if (pid == 0)
- return getuid();
-
p = procfs_file_alloca(pid, "status");
f = fopen(p, "re");
if (!f) {
@@ -478,7 +553,7 @@ int wait_for_terminate(pid_t pid, siginfo_t *status) {
if (errno == EINTR)
continue;
- return -errno;
+ return negative_errno();
}
return 0;
@@ -550,8 +625,10 @@ int kill_and_sigcont(pid_t pid, int sig) {
r = kill(pid, sig) < 0 ? -errno : 0;
- if (r >= 0)
- kill(pid, SIGCONT);
+ /* If this worked, also send SIGCONT, unless we already just sent a SIGCONT, or SIGKILL was sent which isn't
+ * affected by a process being suspended anyway. */
+ if (r >= 0 && !IN_SET(SIGCONT, SIGKILL))
+ (void) kill(pid, SIGCONT);
return r;
}
diff --git a/src/basic/random-util.c b/src/basic/random-util.c
index 2f468db770..ad7b3eedf2 100644
--- a/src/basic/random-util.c
+++ b/src/basic/random-util.c
@@ -46,7 +46,7 @@ int dev_urandom(void *p, size_t n) {
* never block, and will always return some data from the
* kernel, regardless if the random pool is fully initialized
* or not. It thus makes no guarantee for the quality of the
- * returned entropy, but is good enough for or usual usecases
+ * returned entropy, but is good enough for our usual usecases
* of seeding the hash functions for hashtable */
/* Use the getrandom() syscall unless we know we don't have
diff --git a/src/basic/selinux-util.c b/src/basic/selinux-util.c
index 10c2f39369..bc07654668 100644
--- a/src/basic/selinux-util.c
+++ b/src/basic/selinux-util.c
@@ -41,10 +41,10 @@
#include "util.h"
#ifdef HAVE_SELINUX
-DEFINE_TRIVIAL_CLEANUP_FUNC(security_context_t, freecon);
+DEFINE_TRIVIAL_CLEANUP_FUNC(char*, freecon);
DEFINE_TRIVIAL_CLEANUP_FUNC(context_t, context_free);
-#define _cleanup_security_context_free_ _cleanup_(freeconp)
+#define _cleanup_freecon_ _cleanup_(freeconp)
#define _cleanup_context_free_ _cleanup_(context_freep)
static int cached_use = -1;
@@ -143,7 +143,7 @@ int mac_selinux_fix(const char *path, bool ignore_enoent, bool ignore_erofs) {
r = lstat(path, &st);
if (r >= 0) {
- _cleanup_security_context_free_ security_context_t fcon = NULL;
+ _cleanup_freecon_ char* fcon = NULL;
r = selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode);
@@ -186,7 +186,7 @@ int mac_selinux_apply(const char *path, const char *label) {
assert(path);
assert(label);
- if (setfilecon(path, (security_context_t) label) < 0) {
+ if (setfilecon(path, label) < 0) {
log_enforcing("Failed to set SELinux security context %s on path %s: %m", label, path);
if (security_getenforce() > 0)
return -errno;
@@ -199,7 +199,7 @@ int mac_selinux_get_create_label_from_exe(const char *exe, char **label) {
int r = -EOPNOTSUPP;
#ifdef HAVE_SELINUX
- _cleanup_security_context_free_ security_context_t mycon = NULL, fcon = NULL;
+ _cleanup_freecon_ char *mycon = NULL, *fcon = NULL;
security_class_t sclass;
assert(exe);
@@ -217,7 +217,7 @@ int mac_selinux_get_create_label_from_exe(const char *exe, char **label) {
return -errno;
sclass = string_to_security_class("process");
- r = security_compute_create_raw(mycon, fcon, sclass, (security_context_t *) label);
+ r = security_compute_create_raw(mycon, fcon, sclass, label);
if (r < 0)
return -errno;
#endif
@@ -246,7 +246,7 @@ int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *
int r = -EOPNOTSUPP;
#ifdef HAVE_SELINUX
- _cleanup_security_context_free_ security_context_t mycon = NULL, peercon = NULL, fcon = NULL;
+ _cleanup_freecon_ char *mycon = NULL, *peercon = NULL, *fcon = NULL;
_cleanup_context_free_ context_t pcon = NULL, bcon = NULL;
security_class_t sclass;
const char *range = NULL;
@@ -296,7 +296,7 @@ int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *
return -ENOMEM;
sclass = string_to_security_class("process");
- r = security_compute_create_raw(mycon, fcon, sclass, (security_context_t *) label);
+ r = security_compute_create_raw(mycon, fcon, sclass, label);
if (r < 0)
return -errno;
#endif
@@ -314,7 +314,7 @@ char* mac_selinux_free(char *label) {
return NULL;
- freecon((security_context_t) label);
+ freecon(label);
#endif
return NULL;
@@ -323,7 +323,7 @@ char* mac_selinux_free(char *label) {
int mac_selinux_create_file_prepare(const char *path, mode_t mode) {
#ifdef HAVE_SELINUX
- _cleanup_security_context_free_ security_context_t filecon = NULL;
+ _cleanup_freecon_ char *filecon = NULL;
int r;
assert(path);
@@ -383,7 +383,7 @@ int mac_selinux_create_socket_prepare(const char *label) {
assert(label);
- if (setsockcreatecon((security_context_t) label) < 0) {
+ if (setsockcreatecon(label) < 0) {
log_enforcing("Failed to set SELinux security context %s for sockets: %m", label);
if (security_getenforce() == 1)
@@ -411,7 +411,7 @@ int mac_selinux_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
/* Binds a socket and label its file system object according to the SELinux policy */
#ifdef HAVE_SELINUX
- _cleanup_security_context_free_ security_context_t fcon = NULL;
+ _cleanup_freecon_ char *fcon = NULL;
const struct sockaddr_un *un;
bool context_changed = false;
char *path;
diff --git a/src/basic/set.h b/src/basic/set.h
index e0d9dd001c..12f64a8c57 100644
--- a/src/basic/set.h
+++ b/src/basic/set.h
@@ -19,6 +19,7 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#include "extract-word.h"
#include "hashmap.h"
#include "macro.h"
@@ -122,6 +123,7 @@ static inline char **set_get_strv(Set *s) {
int set_consume(Set *s, void *value);
int set_put_strdup(Set *s, const char *p);
int set_put_strdupv(Set *s, char **l);
+int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags flags);
#define SET_FOREACH(e, s, i) \
for ((i) = ITERATOR_FIRST; set_iterate((s), &(i), (void**)&(e)); )
diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c
index c8769a54f4..6093e47172 100644
--- a/src/basic/socket-util.c
+++ b/src/basic/socket-util.c
@@ -85,7 +85,7 @@ int socket_address_parse(SocketAddress *a, const char *s) {
return -EINVAL;
a->sockaddr.in6.sin6_family = AF_INET6;
- a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+ a->sockaddr.in6.sin6_port = htobe16((uint16_t)u);
a->size = sizeof(struct sockaddr_in6);
} else if (*s == '/') {
@@ -133,7 +133,7 @@ int socket_address_parse(SocketAddress *a, const char *s) {
if (r > 0) {
/* Gotcha, it's a traditional IPv4 address */
a->sockaddr.in.sin_family = AF_INET;
- a->sockaddr.in.sin_port = htons((uint16_t) u);
+ a->sockaddr.in.sin_port = htobe16((uint16_t)u);
a->size = sizeof(struct sockaddr_in);
} else {
unsigned idx;
@@ -147,7 +147,7 @@ int socket_address_parse(SocketAddress *a, const char *s) {
return -EINVAL;
a->sockaddr.in6.sin6_family = AF_INET6;
- a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+ a->sockaddr.in6.sin6_port = htobe16((uint16_t)u);
a->sockaddr.in6.sin6_scope_id = idx;
a->sockaddr.in6.sin6_addr = in6addr_any;
a->size = sizeof(struct sockaddr_in6);
@@ -164,12 +164,12 @@ int socket_address_parse(SocketAddress *a, const char *s) {
if (socket_ipv6_is_supported()) {
a->sockaddr.in6.sin6_family = AF_INET6;
- a->sockaddr.in6.sin6_port = htons((uint16_t) u);
+ a->sockaddr.in6.sin6_port = htobe16((uint16_t)u);
a->sockaddr.in6.sin6_addr = in6addr_any;
a->size = sizeof(struct sockaddr_in6);
} else {
a->sockaddr.in.sin_family = AF_INET;
- a->sockaddr.in.sin_port = htons((uint16_t) u);
+ a->sockaddr.in.sin_port = htobe16((uint16_t)u);
a->sockaddr.in.sin_addr.s_addr = INADDR_ANY;
a->size = sizeof(struct sockaddr_in);
}
@@ -488,9 +488,7 @@ int sockaddr_port(const struct sockaddr *_sa) {
if (!IN_SET(sa->sa.sa_family, AF_INET, AF_INET6))
return -EAFNOSUPPORT;
- return ntohs(sa->sa.sa_family == AF_INET6 ?
- sa->in6.sin6_port :
- sa->in.sin_port);
+ return be16toh(sa->sa.sa_family == AF_INET6 ? sa->in6.sin6_port : sa->in.sin_port);
}
int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret) {
@@ -506,13 +504,13 @@ int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_
case AF_INET: {
uint32_t a;
- a = ntohl(sa->in.sin_addr.s_addr);
+ a = be32toh(sa->in.sin_addr.s_addr);
if (include_port)
r = asprintf(&p,
"%u.%u.%u.%u:%u",
a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF,
- ntohs(sa->in.sin_port));
+ be16toh(sa->in.sin_port));
else
r = asprintf(&p,
"%u.%u.%u.%u",
@@ -534,7 +532,7 @@ int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_
r = asprintf(&p,
"%u.%u.%u.%u:%u",
a[0], a[1], a[2], a[3],
- ntohs(sa->in6.sin6_port));
+ be16toh(sa->in6.sin6_port));
else
r = asprintf(&p,
"%u.%u.%u.%u",
@@ -550,7 +548,7 @@ int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_
r = asprintf(&p,
"[%s]:%u",
a,
- ntohs(sa->in6.sin6_port));
+ be16toh(sa->in6.sin6_port));
if (r < 0)
return -ENOMEM;
} else {
@@ -988,7 +986,7 @@ ssize_t next_datagram_size_fd(int fd) {
l = recv(fd, NULL, 0, MSG_PEEK|MSG_TRUNC);
if (l < 0) {
- if (errno == EOPNOTSUPP)
+ if (errno == EOPNOTSUPP || errno == EFAULT)
goto fallback;
return -errno;
@@ -1048,3 +1046,17 @@ int flush_accept(int fd) {
close(cfd);
}
}
+
+struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length) {
+ struct cmsghdr *cmsg;
+
+ assert(mh);
+
+ CMSG_FOREACH(cmsg, mh)
+ if (cmsg->cmsg_level == level &&
+ cmsg->cmsg_type == type &&
+ (length == (socklen_t) -1 || length == cmsg->cmsg_len))
+ return cmsg;
+
+ return NULL;
+}
diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h
index e9230e4a9f..2536b085f9 100644
--- a/src/basic/socket-util.h
+++ b/src/basic/socket-util.h
@@ -142,6 +142,8 @@ int flush_accept(int fd);
#define CMSG_FOREACH(cmsg, mh) \
for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg)))
+struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
+
/* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */
#define SOCKADDR_UN_LEN(sa) \
({ \
diff --git a/src/basic/string-table.h b/src/basic/string-table.h
index d88625fca7..369610efc8 100644
--- a/src/basic/string-table.h
+++ b/src/basic/string-table.h
@@ -48,6 +48,8 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \
scope type name##_from_string(const char *s) { \
int b; \
+ if (!s) \
+ return -1; \
b = parse_boolean(s); \
if (b == 0) \
return (type) 0; \
diff --git a/src/basic/string-util.c b/src/basic/string-util.c
index 293a15f9c0..5d4510e1b3 100644
--- a/src/basic/string-util.c
+++ b/src/basic/string-util.c
@@ -22,6 +22,7 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include "alloc-util.h"
#include "gunicode.h"
@@ -323,6 +324,14 @@ char ascii_tolower(char x) {
return x;
}
+char ascii_toupper(char x) {
+
+ if (x >= 'a' && x <= 'z')
+ return x - 'a' + 'A';
+
+ return x;
+}
+
char *ascii_strlower(char *t) {
char *p;
@@ -334,6 +343,17 @@ char *ascii_strlower(char *t) {
return t;
}
+char *ascii_strupper(char *t) {
+ char *p;
+
+ assert(t);
+
+ for (p = t; *p; p++)
+ *p = ascii_toupper(*p);
+
+ return t;
+}
+
char *ascii_strlower_n(char *t, size_t n) {
size_t i;
@@ -803,25 +823,20 @@ int free_and_strdup(char **p, const char *s) {
return 1;
}
-#pragma GCC push_options
-#pragma GCC optimize("O0")
+/*
+ * Pointer to memset is volatile so that compiler must de-reference
+ * the pointer and can't assume that it points to any function in
+ * particular (such as memset, which it then might further "optimize")
+ * This approach is inspired by openssl's crypto/mem_clr.c.
+ */
+typedef void *(*memset_t)(void *,int,size_t);
-void* memory_erase(void *p, size_t l) {
- volatile uint8_t* x = (volatile uint8_t*) p;
+static volatile memset_t memset_func = memset;
- /* This basically does what memset() does, but hopefully isn't
- * optimized away by the compiler. One of those days, when
- * glibc learns memset_s() we should replace this call by
- * memset_s(), but until then this has to do. */
-
- for (; l > 0; l--)
- *(x++) = 'x';
-
- return p;
+void* memory_erase(void *p, size_t l) {
+ return memset_func(p, 'x', l);
}
-#pragma GCC pop_options
-
char* string_erase(char *x) {
if (!x)
diff --git a/src/basic/string-util.h b/src/basic/string-util.h
index 1209e1e2e1..b75aba63c2 100644
--- a/src/basic/string-util.h
+++ b/src/basic/string-util.h
@@ -137,6 +137,9 @@ char ascii_tolower(char x);
char *ascii_strlower(char *s);
char *ascii_strlower_n(char *s, size_t n);
+char ascii_toupper(char x);
+char *ascii_strupper(char *s);
+
int ascii_strcasecmp_n(const char *a, const char *b, size_t n);
int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m);
diff --git a/src/basic/strv.c b/src/basic/strv.c
index 97a96e5762..34e464d253 100644
--- a/src/basic/strv.c
+++ b/src/basic/strv.c
@@ -139,16 +139,16 @@ char **strv_new_ap(const char *x, va_list ap) {
va_list aq;
/* As a special trick we ignore all listed strings that equal
- * (const char*) -1. This is supposed to be used with the
+ * STRV_IGNORE. This is supposed to be used with the
* STRV_IFNOTNULL() macro to include possibly NULL strings in
* the string list. */
if (x) {
- n = x == (const char*) -1 ? 0 : 1;
+ n = x == STRV_IGNORE ? 0 : 1;
va_copy(aq, ap);
while ((s = va_arg(aq, const char*))) {
- if (s == (const char*) -1)
+ if (s == STRV_IGNORE)
continue;
n++;
@@ -162,7 +162,7 @@ char **strv_new_ap(const char *x, va_list ap) {
return NULL;
if (x) {
- if (x != (const char*) -1) {
+ if (x != STRV_IGNORE) {
a[i] = strdup(x);
if (!a[i])
goto fail;
@@ -171,7 +171,7 @@ char **strv_new_ap(const char *x, va_list ap) {
while ((s = va_arg(ap, const char*))) {
- if (s == (const char*) -1)
+ if (s == STRV_IGNORE)
continue;
a[i] = strdup(s);
@@ -638,6 +638,17 @@ char **strv_remove(char **l, const char *s) {
}
char **strv_parse_nulstr(const char *s, size_t l) {
+ /* l is the length of the input data, which will be split at NULs into
+ * elements of the resulting strv. Hence, the number of items in the resulting strv
+ * will be equal to one plus the number of NUL bytes in the l bytes starting at s,
+ * unless s[l-1] is NUL, in which case the final empty string is not stored in
+ * the resulting strv, and length is equal to the number of NUL bytes.
+ *
+ * Note that contrary to a normal nulstr which cannot contain empty strings, because
+ * the input data is terminated by any two consequent NUL bytes, this parser accepts
+ * empty strings in s.
+ */
+
const char *p;
unsigned c = 0, i = 0;
char **v;
@@ -700,6 +711,13 @@ char **strv_split_nulstr(const char *s) {
}
int strv_make_nulstr(char **l, char **p, size_t *q) {
+ /* A valid nulstr with two NULs at the end will be created, but
+ * q will be the length without the two trailing NULs. Thus the output
+ * string is a valid nulstr and can be iterated over using NULSTR_FOREACH,
+ * and can also be parsed by strv_parse_nulstr as long as the length
+ * is provided separately.
+ */
+
size_t n_allocated = 0, n = 0;
_cleanup_free_ char *m = NULL;
char **i;
@@ -712,7 +730,7 @@ int strv_make_nulstr(char **l, char **p, size_t *q) {
z = strlen(*i);
- if (!GREEDY_REALLOC(m, n_allocated, n + z + 1))
+ if (!GREEDY_REALLOC(m, n_allocated, n + z + 2))
return -ENOMEM;
memcpy(m + n, *i, z + 1);
@@ -723,11 +741,14 @@ int strv_make_nulstr(char **l, char **p, size_t *q) {
m = new0(char, 1);
if (!m)
return -ENOMEM;
- n = 0;
- }
+ n = 1;
+ } else
+ /* make sure there is a second extra NUL at the end of resulting nulstr */
+ m[n] = '\0';
+ assert(n > 0);
*p = m;
- *q = n;
+ *q = n - 1;
m = NULL;
@@ -803,13 +824,8 @@ char **strv_reverse(char **l) {
if (n <= 1)
return l;
- for (i = 0; i < n / 2; i++) {
- char *t;
-
- t = l[i];
- l[i] = l[n-1-i];
- l[n-1-i] = t;
- }
+ for (i = 0; i < n / 2; i++)
+ SWAP_TWO(l[i], l[n-1-i]);
return l;
}
@@ -838,7 +854,7 @@ bool strv_fnmatch(char* const* patterns, const char *s, int flags) {
char* const* p;
STRV_FOREACH(p, patterns)
- if (fnmatch(*p, s, 0) == 0)
+ if (fnmatch(*p, s, flags) == 0)
return true;
return false;
@@ -880,7 +896,7 @@ int strv_extend_n(char ***l, const char *value, size_t n) {
if (n == 0)
return 0;
- /* Adds the value value n times to l */
+ /* Adds the value n times to l */
k = strv_length(*l);
diff --git a/src/basic/strv.h b/src/basic/strv.h
index f61bbb5386..683ce83a2a 100644
--- a/src/basic/strv.h
+++ b/src/basic/strv.h
@@ -69,8 +69,10 @@ bool strv_equal(char **a, char **b);
char **strv_new(const char *x, ...) _sentinel_;
char **strv_new_ap(const char *x, va_list ap);
+#define STRV_IGNORE ((const char *) -1)
+
static inline const char* STRV_IFNOTNULL(const char *x) {
- return x ? x : (const char *) -1;
+ return x ? x : STRV_IGNORE;
}
static inline bool strv_isempty(char * const *l) {
diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c
index d8cca55378..f0a46c48cf 100644
--- a/src/basic/terminal-util.c
+++ b/src/basic/terminal-util.c
@@ -785,7 +785,7 @@ bool tty_is_vc_resolve(const char *tty) {
}
const char *default_term_for_tty(const char *tty) {
- return tty && tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220";
+ return tty && tty_is_vc_resolve(tty) ? "linux" : "vt220";
}
int fd_columns(int fd) {
@@ -888,9 +888,7 @@ int make_stdio(int fd) {
/* Explicitly unset O_CLOEXEC, since if fd was < 3, then
* dup2() was a NOP and the bit hence possibly set. */
- fd_cloexec(STDIN_FILENO, false);
- fd_cloexec(STDOUT_FILENO, false);
- fd_cloexec(STDERR_FILENO, false);
+ stdio_unset_cloexec();
return 0;
}
diff --git a/src/basic/time-util.c b/src/basic/time-util.c
index edd9179cb8..0ef1f6393e 100644
--- a/src/basic/time-util.c
+++ b/src/basic/time-util.c
@@ -87,6 +87,16 @@ dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
return ts;
}
+triple_timestamp* triple_timestamp_get(triple_timestamp *ts) {
+ assert(ts);
+
+ ts->realtime = now(CLOCK_REALTIME);
+ ts->monotonic = now(CLOCK_MONOTONIC);
+ ts->boottime = clock_boottime_supported() ? now(CLOCK_BOOTTIME) : USEC_INFINITY;
+
+ return ts;
+}
+
dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
int64_t delta;
assert(ts);
@@ -104,6 +114,24 @@ dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
return ts;
}
+triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) {
+ int64_t delta;
+
+ assert(ts);
+
+ if (u == USEC_INFINITY || u <= 0) {
+ ts->realtime = ts->monotonic = ts->boottime = u;
+ return ts;
+ }
+
+ ts->realtime = u;
+ delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
+ ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta);
+ ts->boottime = clock_boottime_supported() ? usec_sub(now(CLOCK_BOOTTIME), delta) : USEC_INFINITY;
+
+ return ts;
+}
+
dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
int64_t delta;
assert(ts);
@@ -136,6 +164,26 @@ dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, us
return ts;
}
+usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
+
+ switch (clock) {
+
+ case CLOCK_REALTIME:
+ case CLOCK_REALTIME_ALARM:
+ return ts->realtime;
+
+ case CLOCK_MONOTONIC:
+ return ts->monotonic;
+
+ case CLOCK_BOOTTIME:
+ case CLOCK_BOOTTIME_ALARM:
+ return ts->boottime;
+
+ default:
+ return USEC_INFINITY;
+ }
+}
+
usec_t timespec_load(const struct timespec *ts) {
assert(ts);
@@ -206,32 +254,95 @@ struct timeval *timeval_store(struct timeval *tv, usec_t u) {
return tv;
}
-static char *format_timestamp_internal(char *buf, size_t l, usec_t t,
- bool utc, bool us) {
+static char *format_timestamp_internal(
+ char *buf,
+ size_t l,
+ usec_t t,
+ bool utc,
+ bool us) {
+
+ /* The weekdays in non-localized (English) form. We use this instead of the localized form, so that our
+ * generated timestamps may be parsed with parse_timestamp(), and always read the same. */
+ static const char * const weekdays[] = {
+ [0] = "Sun",
+ [1] = "Mon",
+ [2] = "Tue",
+ [3] = "Wed",
+ [4] = "Thu",
+ [5] = "Fri",
+ [6] = "Sat",
+ };
+
struct tm tm;
time_t sec;
- int k;
+ size_t n;
assert(buf);
- assert(l > 0);
+ if (l <
+ 3 + /* week day */
+ 1 + 10 + /* space and date */
+ 1 + 8 + /* space and time */
+ (us ? 1 + 6 : 0) + /* "." and microsecond part */
+ 1 + 1 + /* space and shortest possible zone */
+ 1)
+ return NULL; /* Not enough space even for the shortest form. */
if (t <= 0 || t == USEC_INFINITY)
+ return NULL; /* Timestamp is unset */
+
+ sec = (time_t) (t / USEC_PER_SEC); /* Round down */
+ if ((usec_t) sec != (t / USEC_PER_SEC))
+ return NULL; /* overflow? */
+
+ if (!localtime_or_gmtime_r(&sec, &tm, utc))
return NULL;
- sec = (time_t) (t / USEC_PER_SEC);
- localtime_or_gmtime_r(&sec, &tm, utc);
+ /* Start with the week day */
+ assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays));
+ memcpy(buf, weekdays[tm.tm_wday], 4);
- if (us)
- k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S", &tm);
- else
- k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", &tm);
+ /* Add the main components */
+ if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0)
+ return NULL; /* Doesn't fit */
- if (k <= 0)
- return NULL;
+ /* Append the microseconds part, if that's requested */
if (us) {
- snprintf(buf + strlen(buf), l - strlen(buf), ".%06llu", (unsigned long long) (t % USEC_PER_SEC));
- if (strftime(buf + strlen(buf), l - strlen(buf), " %Z", &tm) <= 0)
- return NULL;
+ n = strlen(buf);
+ if (n + 8 > l)
+ return NULL; /* Microseconds part doesn't fit. */
+
+ sprintf(buf + n, ".%06llu", (unsigned long long) (t % USEC_PER_SEC));
+ }
+
+ /* Append the timezone */
+ n = strlen(buf);
+ if (utc) {
+ /* If this is UTC then let's explicitly use the "UTC" string here, because gmtime_r() normally uses the
+ * obsolete "GMT" instead. */
+ if (n + 5 > l)
+ return NULL; /* "UTC" doesn't fit. */
+
+ strcpy(buf + n, " UTC");
+
+ } else if (!isempty(tm.tm_zone)) {
+ size_t tn;
+
+ /* An explicit timezone is specified, let's use it, if it fits */
+ tn = strlen(tm.tm_zone);
+ if (n + 1 + tn + 1 > l) {
+ /* The full time zone does not fit in. Yuck. */
+
+ if (n + 1 + _POSIX_TZNAME_MAX + 1 > l)
+ return NULL; /* Not even enough space for the POSIX minimum (of 6)? In that case, complain that it doesn't fit */
+
+ /* So the time zone doesn't fit in fully, but the caller passed enough space for the POSIX
+ * minimum time zone length. In this case suppress the timezone entirely, in order not to dump
+ * an overly long, hard to read string on the user. This should be safe, because the user will
+ * assume the local timezone anyway if none is shown. And so does parse_timestamp(). */
+ } else {
+ buf[n++] = ' ';
+ strcpy(buf + n, tm.tm_zone);
+ }
}
return buf;
@@ -491,12 +602,11 @@ int parse_timestamp(const char *t, usec_t *usec) {
{ "Sat", 6 },
};
- const char *k;
- const char *utc;
+ const char *k, *utc, *tzn = NULL;
struct tm tm, copy;
time_t x;
usec_t x_usec, plus = 0, minus = 0, ret;
- int r, weekday = -1;
+ int r, weekday = -1, dst = -1;
unsigned i;
/*
@@ -561,15 +671,55 @@ int parse_timestamp(const char *t, usec_t *usec) {
goto finish;
}
+ /* See if the timestamp is suffixed with UTC */
utc = endswith_no_case(t, " UTC");
if (utc)
t = strndupa(t, utc - t);
+ else {
+ const char *e = NULL;
+ int j;
+
+ tzset();
+
+ /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only
+ * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because
+ * there are no nice APIs available to cover this. By accepting the local time zone strings, we make
+ * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't
+ * support arbitrary timezone specifications. */
+
+ for (j = 0; j <= 1; j++) {
- x = ret / USEC_PER_SEC;
+ if (isempty(tzname[j]))
+ continue;
+
+ e = endswith_no_case(t, tzname[j]);
+ if (!e)
+ continue;
+ if (e == t)
+ continue;
+ if (e[-1] != ' ')
+ continue;
+
+ break;
+ }
+
+ if (IN_SET(j, 0, 1)) {
+ /* Found one of the two timezones specified. */
+ t = strndupa(t, e - t - 1);
+ dst = j;
+ tzn = tzname[j];
+ }
+ }
+
+ x = (time_t) (ret / USEC_PER_SEC);
x_usec = 0;
- assert_se(localtime_or_gmtime_r(&x, &tm, utc));
- tm.tm_isdst = -1;
+ if (!localtime_or_gmtime_r(&x, &tm, utc))
+ return -EINVAL;
+
+ tm.tm_isdst = dst;
+ if (tzn)
+ tm.tm_zone = tzn;
if (streq(t, "today")) {
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
@@ -586,7 +736,6 @@ int parse_timestamp(const char *t, usec_t *usec) {
goto from_tm;
}
-
for (i = 0; i < ELEMENTSOF(day_nr); i++) {
size_t skip;
@@ -679,7 +828,6 @@ parse_usec:
return -EINVAL;
x_usec = add;
-
}
from_tm:
@@ -1107,6 +1255,30 @@ clockid_t clock_boottime_or_monotonic(void) {
return CLOCK_MONOTONIC;
}
+bool clock_supported(clockid_t clock) {
+ struct timespec ts;
+
+ switch (clock) {
+
+ case CLOCK_MONOTONIC:
+ case CLOCK_REALTIME:
+ return true;
+
+ case CLOCK_BOOTTIME:
+ return clock_boottime_supported();
+
+ case CLOCK_BOOTTIME_ALARM:
+ if (!clock_boottime_supported())
+ return false;
+
+ /* fall through, after checking the cached value for CLOCK_BOOTTIME. */
+
+ default:
+ /* For everything else, check properly */
+ return clock_gettime(clock, &ts) >= 0;
+ }
+}
+
int get_timezone(char **tz) {
_cleanup_free_ char *t = NULL;
const char *e;
diff --git a/src/basic/time-util.h b/src/basic/time-util.h
index a5e3f567ec..99be5ce6ee 100644
--- a/src/basic/time-util.h
+++ b/src/basic/time-util.h
@@ -39,6 +39,12 @@ typedef struct dual_timestamp {
usec_t monotonic;
} dual_timestamp;
+typedef struct triple_timestamp {
+ usec_t realtime;
+ usec_t monotonic;
+ usec_t boottime;
+} triple_timestamp;
+
#define USEC_INFINITY ((usec_t) -1)
#define NSEC_INFINITY ((nsec_t) -1)
@@ -62,14 +68,17 @@ typedef struct dual_timestamp {
#define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC))
#define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC))
-#define FORMAT_TIMESTAMP_MAX ((4*4+1)+11+9+4+1) /* weekdays can be unicode */
+/* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this
+ * to 6. Let's rely on that. */
+#define FORMAT_TIMESTAMP_MAX (3+1+10+1+8+1+6+1+6+1)
#define FORMAT_TIMESTAMP_WIDTH 28 /* when outputting, assume this width */
#define FORMAT_TIMESTAMP_RELATIVE_MAX 256
#define FORMAT_TIMESPAN_MAX 64
#define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1)
-#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) { 0ULL, 0ULL })
+#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) {})
+#define TRIPLE_TIMESTAMP_NULL ((struct triple_timestamp) {})
usec_t now(clockid_t clock);
nsec_t now_nsec(clockid_t clock);
@@ -79,11 +88,28 @@ dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u);
dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u);
dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u);
+triple_timestamp* triple_timestamp_get(triple_timestamp *ts);
+triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u);
+
+#define DUAL_TIMESTAMP_HAS_CLOCK(clock) \
+ IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC)
+
+#define TRIPLE_TIMESTAMP_HAS_CLOCK(clock) \
+ IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM)
+
static inline bool dual_timestamp_is_set(dual_timestamp *ts) {
return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
(ts->monotonic > 0 && ts->monotonic != USEC_INFINITY));
}
+static inline bool triple_timestamp_is_set(triple_timestamp *ts) {
+ return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
+ (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY) ||
+ (ts->boottime > 0 && ts->boottime != USEC_INFINITY));
+}
+
+usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock);
+
usec_t timespec_load(const struct timespec *ts) _pure_;
struct timespec *timespec_store(struct timespec *ts, usec_t u);
@@ -113,6 +139,7 @@ int get_timezones(char ***l);
bool timezone_is_valid(const char *name);
bool clock_boottime_supported(void);
+bool clock_supported(clockid_t clock);
clockid_t clock_boottime_or_monotonic(void);
#define xstrftime(buf, fmt, tm) \
diff --git a/src/basic/unaligned.h b/src/basic/unaligned.h
index 79be645bed..7c847a3ccb 100644
--- a/src/basic/unaligned.h
+++ b/src/basic/unaligned.h
@@ -109,3 +109,21 @@ static inline void unaligned_write_le64(void *_u, uint64_t a) {
unaligned_write_le32(u, (uint32_t) a);
unaligned_write_le32(u + 4, (uint32_t) (a >> 32));
}
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define unaligned_read_ne16 unaligned_read_be16
+#define unaligned_read_ne32 unaligned_read_be32
+#define unaligned_read_ne64 unaligned_read_be64
+
+#define unaligned_write_ne16 unaligned_write_be16
+#define unaligned_write_ne32 unaligned_write_be32
+#define unaligned_write_ne64 unaligned_write_be64
+#else
+#define unaligned_read_ne16 unaligned_read_le16
+#define unaligned_read_ne32 unaligned_read_le32
+#define unaligned_read_ne64 unaligned_read_le64
+
+#define unaligned_write_ne16 unaligned_write_le16
+#define unaligned_write_ne32 unaligned_write_le32
+#define unaligned_write_ne64 unaligned_write_le64
+#endif
diff --git a/src/basic/unit-name.h b/src/basic/unit-name.h
index f209a84634..44eadf0347 100644
--- a/src/basic/unit-name.h
+++ b/src/basic/unit-name.h
@@ -195,7 +195,6 @@ typedef enum SwapState {
_SWAP_STATE_INVALID = -1
} SwapState;
-
typedef enum TargetState {
TARGET_DEAD,
TARGET_ACTIVE,
diff --git a/src/basic/user-util.c b/src/basic/user-util.c
index f65ca3edaa..122d9a0c7c 100644
--- a/src/basic/user-util.c
+++ b/src/basic/user-util.c
@@ -29,6 +29,7 @@
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
+#include <utmp.h>
#include "missing.h"
#include "alloc-util.h"
@@ -39,6 +40,7 @@
#include "path-util.h"
#include "string-util.h"
#include "user-util.h"
+#include "utf8.h"
bool uid_is_valid(uid_t uid) {
@@ -458,7 +460,7 @@ int take_etc_passwd_lock(const char *root) {
*
* Note that shadow-utils also takes per-database locks in
* addition to lckpwdf(). However, we don't given that they
- * are redundant as they they invoke lckpwdf() first and keep
+ * are redundant as 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. */
@@ -479,3 +481,94 @@ int take_etc_passwd_lock(const char *root) {
return fd;
}
+
+bool valid_user_group_name(const char *u) {
+ const char *i;
+ long sz;
+
+ /* Checks if the specified name is a valid user/group name. */
+
+ if (isempty(u))
+ return false;
+
+ if (!(u[0] >= 'a' && u[0] <= 'z') &&
+ !(u[0] >= 'A' && u[0] <= 'Z') &&
+ u[0] != '_')
+ return false;
+
+ for (i = u+1; *i; i++) {
+ if (!(*i >= 'a' && *i <= 'z') &&
+ !(*i >= 'A' && *i <= 'Z') &&
+ !(*i >= '0' && *i <= '9') &&
+ *i != '_' &&
+ *i != '-')
+ return false;
+ }
+
+ sz = sysconf(_SC_LOGIN_NAME_MAX);
+ assert_se(sz > 0);
+
+ if ((size_t) (i-u) > (size_t) sz)
+ return false;
+
+ if ((size_t) (i-u) > UT_NAMESIZE - 1)
+ return false;
+
+ return true;
+}
+
+bool valid_user_group_name_or_id(const char *u) {
+
+ /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the right
+ * range, and not the invalid user ids. */
+
+ if (isempty(u))
+ return false;
+
+ if (valid_user_group_name(u))
+ return true;
+
+ return parse_uid(u, NULL) >= 0;
+}
+
+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;
+}
+
+bool valid_home(const char *p) {
+
+ 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_safe(p))
+ return false;
+
+ /* Colons are used as field separators, and hence not OK */
+ if (strchr(p, ':'))
+ return false;
+
+ return true;
+}
diff --git a/src/basic/user-util.h b/src/basic/user-util.h
index 8026eca3f4..36f71fb004 100644
--- a/src/basic/user-util.h
+++ b/src/basic/user-util.h
@@ -68,3 +68,8 @@ int take_etc_passwd_lock(const char *root);
static inline bool userns_supported(void) {
return access("/proc/self/uid_map", F_OK) >= 0;
}
+
+bool valid_user_group_name(const char *u);
+bool valid_user_group_name_or_id(const char *u);
+bool valid_gecos(const char *d);
+bool valid_home(const char *p);
diff --git a/src/basic/util.c b/src/basic/util.c
index 756c663be4..9d66d28eb7 100644
--- a/src/basic/util.c
+++ b/src/basic/util.c
@@ -36,6 +36,7 @@
#include "alloc-util.h"
#include "build.h"
+#include "cgroup-util.h"
#include "def.h"
#include "dirent-util.h"
#include "fd-util.h"
@@ -64,6 +65,7 @@ assert_cc(EAGAIN == EWOULDBLOCK);
int saved_argc = 0;
char **saved_argv = NULL;
+static int saved_in_initrd = -1;
size_t page_size(void) {
static thread_local size_t pgsz = 0;
@@ -454,11 +456,10 @@ int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *pa
}
bool in_initrd(void) {
- static int saved = -1;
struct statfs s;
- if (saved >= 0)
- return saved;
+ if (saved_in_initrd >= 0)
+ return saved_in_initrd;
/* We make two checks here:
*
@@ -470,11 +471,15 @@ bool in_initrd(void) {
* emptying when transititioning to the main systemd.
*/
- saved = access("/etc/initrd-release", F_OK) >= 0 &&
- statfs("/", &s) >= 0 &&
- is_temporary_fs(&s);
+ saved_in_initrd = access("/etc/initrd-release", F_OK) >= 0 &&
+ statfs("/", &s) >= 0 &&
+ is_temporary_fs(&s);
- return saved;
+ return saved_in_initrd;
+}
+
+void in_initrd_force(bool value) {
+ saved_in_initrd = value;
}
/* hey glibc, APIs with callbacks without a user pointer are so useless */
@@ -576,47 +581,6 @@ int on_ac_power(void) {
return found_online || !found_offline;
}
-bool id128_is_valid(const char *s) {
- size_t i, l;
-
- l = strlen(s);
- if (l == 32) {
-
- /* Simple formatted 128bit hex string */
-
- for (i = 0; i < l; i++) {
- char c = s[i];
-
- if (!(c >= '0' && c <= '9') &&
- !(c >= 'a' && c <= 'z') &&
- !(c >= 'A' && c <= 'Z'))
- return false;
- }
-
- } else if (l == 36) {
-
- /* Formatted UUID */
-
- for (i = 0; i < l; i++) {
- char c = s[i];
-
- if ((i == 8 || i == 13 || i == 18 || i == 23)) {
- if (c != '-')
- return false;
- } else {
- if (!(c >= '0' && c <= '9') &&
- !(c >= 'a' && c <= 'z') &&
- !(c >= 'A' && c <= 'Z'))
- return false;
- }
- }
-
- } else
- return false;
-
- return true;
-}
-
int container_get_leader(const char *machine, pid_t *pid) {
_cleanup_free_ char *s = NULL, *class = NULL;
const char *p;
@@ -767,15 +731,119 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
}
uint64_t physical_memory(void) {
- long mem;
+ _cleanup_free_ char *root = NULL, *value = NULL;
+ uint64_t mem, lim;
+ size_t ps;
+ long sc;
+
+ /* We return this as uint64_t in case we are running as 32bit process on a 64bit kernel with huge amounts of
+ * memory.
+ *
+ * In order to support containers nicely that have a configured memory limit we'll take the minimum of the
+ * physically reported amount of memory and the limit configured for the root cgroup, if there is any. */
+
+ sc = sysconf(_SC_PHYS_PAGES);
+ assert(sc > 0);
+
+ ps = page_size();
+ mem = (uint64_t) sc * (uint64_t) ps;
+
+ if (cg_get_root_path(&root) < 0)
+ return mem;
+
+ if (cg_get_attribute("memory", root, "memory.limit_in_bytes", &value))
+ return mem;
+
+ if (safe_atou64(value, &lim) < 0)
+ return mem;
+
+ /* Make sure the limit is a multiple of our own page size */
+ lim /= ps;
+ lim *= ps;
+
+ return MIN(mem, lim);
+}
+
+uint64_t physical_memory_scale(uint64_t v, uint64_t max) {
+ uint64_t p, m, ps, r;
+
+ assert(max > 0);
+
+ /* Returns the physical memory size, multiplied by v divided by max. Returns UINT64_MAX on overflow. On success
+ * the result is a multiple of the page size (rounds down). */
+
+ ps = page_size();
+ assert(ps > 0);
+
+ p = physical_memory() / ps;
+ assert(p > 0);
+
+ m = p * v;
+ if (m / p != v)
+ return UINT64_MAX;
+
+ m /= max;
+
+ r = m * ps;
+ if (r / ps != m)
+ return UINT64_MAX;
+
+ return r;
+}
+
+uint64_t system_tasks_max(void) {
+
+#if SIZEOF_PID_T == 4
+#define TASKS_MAX ((uint64_t) (INT32_MAX-1))
+#elif SIZEOF_PID_T == 2
+#define TASKS_MAX ((uint64_t) (INT16_MAX-1))
+#else
+#error "Unknown pid_t size"
+#endif
+
+ _cleanup_free_ char *value = NULL, *root = NULL;
+ uint64_t a = TASKS_MAX, b = TASKS_MAX;
+
+ /* Determine the maximum number of tasks that may run on this system. We check three sources to determine this
+ * limit:
+ *
+ * a) the maximum value for the pid_t type
+ * b) the cgroups pids_max attribute for the system
+ * c) the kernel's configure maximum PID value
+ *
+ * And then pick the smallest of the three */
+
+ if (read_one_line_file("/proc/sys/kernel/pid_max", &value) >= 0)
+ (void) safe_atou64(value, &a);
+
+ if (cg_get_root_path(&root) >= 0) {
+ value = mfree(value);
+
+ if (cg_get_attribute("pids", root, "pids.max", &value) >= 0)
+ (void) safe_atou64(value, &b);
+ }
+
+ return MIN3(TASKS_MAX,
+ a <= 0 ? TASKS_MAX : a,
+ b <= 0 ? TASKS_MAX : b);
+}
+
+uint64_t system_tasks_max_scale(uint64_t v, uint64_t max) {
+ uint64_t t, m;
+
+ assert(max > 0);
+
+ /* Multiply the system's task value by the fraction v/max. Hence, if max==100 this calculates percentages
+ * relative to the system's maximum number of tasks. Returns UINT64_MAX on overflow. */
- /* We return this as uint64_t in case we are running as 32bit
- * process on a 64bit kernel with huge amounts of memory */
+ t = system_tasks_max();
+ assert(t > 0);
- mem = sysconf(_SC_PHYS_PAGES);
- assert(mem > 0);
+ m = t * v;
+ if (m / t != v) /* overflow? */
+ return UINT64_MAX;
- return (uint64_t) mem * (uint64_t) page_size();
+ return m / max;
}
int update_reboot_parameter_and_warn(const char *param) {
diff --git a/src/basic/util.h b/src/basic/util.h
index 1c032c15c9..bb2fc318ef 100644
--- a/src/basic/util.h
+++ b/src/basic/util.h
@@ -61,6 +61,10 @@ static inline const char* one_zero(bool b) {
return b ? "1" : "0";
}
+static inline const char* enable_disable(bool b) {
+ return b ? "enable" : "disable";
+}
+
void execute_directories(const char* const* directories, usec_t timeout, char *argv[]);
bool plymouth_running(void);
@@ -86,6 +90,7 @@ int prot_from_flags(int flags) _const_;
int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...);
bool in_initrd(void);
+void in_initrd_force(bool value);
void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
int (*compar) (const void *, const void *, void *),
@@ -175,14 +180,16 @@ static inline unsigned log2u_round_up(unsigned x) {
return log2u(x - 1) + 1;
}
-bool id128_is_valid(const char *s) _pure_;
-
int container_get_leader(const char *machine, pid_t *pid);
int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd);
int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
uint64_t physical_memory(void);
+uint64_t physical_memory_scale(uint64_t v, uint64_t max);
+
+uint64_t system_tasks_max(void);
+uint64_t system_tasks_max_scale(uint64_t v, uint64_t max);
int update_reboot_parameter_and_warn(const char *param);
diff --git a/src/basic/virt.c b/src/basic/virt.c
index dace1f4328..10a2043746 100644
--- a/src/basic/virt.c
+++ b/src/basic/virt.c
@@ -49,6 +49,8 @@ static int detect_vm_cpuid(void) {
{ "VMwareVMware", VIRTUALIZATION_VMWARE },
/* http://msdn.microsoft.com/en-us/library/ff542428.aspx */
{ "Microsoft Hv", VIRTUALIZATION_MICROSOFT },
+ /* https://wiki.freebsd.org/bhyve */
+ { "bhyve bhyve ", VIRTUALIZATION_BHYVE },
};
uint32_t eax, ecx;
@@ -178,6 +180,8 @@ static int detect_vm_dmi(void) {
{ "Xen", VIRTUALIZATION_XEN },
{ "Bochs", VIRTUALIZATION_BOCHS },
{ "Parallels", VIRTUALIZATION_PARALLELS },
+ /* https://wiki.freebsd.org/bhyve */
+ { "BHYVE", VIRTUALIZATION_BHYVE },
};
unsigned i;
int r;
@@ -502,6 +506,7 @@ static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
[VIRTUALIZATION_MICROSOFT] = "microsoft",
[VIRTUALIZATION_ZVM] = "zvm",
[VIRTUALIZATION_PARALLELS] = "parallels",
+ [VIRTUALIZATION_BHYVE] = "bhyve",
[VIRTUALIZATION_VM_OTHER] = "vm-other",
[VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
diff --git a/src/basic/virt.h b/src/basic/virt.h
index a538f07f6b..bc5b3ae94d 100644
--- a/src/basic/virt.h
+++ b/src/basic/virt.h
@@ -37,6 +37,7 @@ enum {
VIRTUALIZATION_MICROSOFT,
VIRTUALIZATION_ZVM,
VIRTUALIZATION_PARALLELS,
+ VIRTUALIZATION_BHYVE,
VIRTUALIZATION_VM_OTHER,
VIRTUALIZATION_VM_LAST = VIRTUALIZATION_VM_OTHER,
diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c
index d0af41498f..a7cdf92ed2 100644
--- a/src/boot/bootctl.c
+++ b/src/boot/bootctl.c
@@ -26,6 +26,7 @@
#include <ftw.h>
#include <getopt.h>
#include <limits.h>
+#include <linux/magic.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -42,22 +43,53 @@
#include "fd-util.h"
#include "fileio.h"
#include "locale-util.h"
+#include "parse-util.h"
#include "rm-rf.h"
#include "string-util.h"
+#include "strv.h"
+#include "umask-util.h"
#include "util.h"
+#include "verbs.h"
+#include "virt.h"
+#include "stat-util.h"
+
+static char *arg_path = NULL;
+static bool arg_touch_variables = true;
+
+static int verify_esp(
+ bool searching,
+ const char *p,
+ uint32_t *ret_part,
+ uint64_t *ret_pstart,
+ uint64_t *ret_psize,
+ sd_id128_t *ret_uuid) {
-static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) {
- struct statfs sfs;
- struct stat st, st2;
- _cleanup_free_ char *t = NULL;
_cleanup_blkid_free_probe_ blkid_probe b = NULL;
- int r;
+ _cleanup_free_ char *t = NULL;
+ uint64_t pstart = 0, psize = 0;
+ struct stat st, st2;
const char *v, *t2;
+ struct statfs sfs;
+ sd_id128_t uuid = SD_ID128_NULL;
+ uint32_t part = 0;
+ int r;
+
+ assert(p);
+
+ if (statfs(p, &sfs) < 0) {
+
+ /* If we are searching for the mount point, don't generate a log message if we can't find the path */
+ if (errno == ENOENT && searching)
+ return -ENOENT;
- if (statfs(p, &sfs) < 0)
return log_error_errno(errno, "Failed to check file system type of \"%s\": %m", p);
+ }
+
+ if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) {
+
+ if (searching)
+ return -EADDRNOTAVAIL;
- if (sfs.f_type != 0x4d44) {
log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
return -ENODEV;
}
@@ -80,6 +112,11 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
return -ENODEV;
}
+ /* In a container we don't have access to block devices, skip this part of the verification, we trust the
+ * container manager set everything up correctly on its own. */
+ if (detect_container() > 0)
+ goto finish;
+
r = asprintf(&t, "/dev/block/%u:%u", major(st.st_dev), minor(st.st_dev));
if (r < 0)
return log_oom();
@@ -101,7 +138,7 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
errno = 0;
r = blkid_do_safeprobe(b);
if (r == -2) {
- log_error("File system \"%s\" is ambigious.", p);
+ log_error("File system \"%s\" is ambiguous.", p);
return -ENODEV;
} else if (r == 1) {
log_error("File system \"%s\" does not contain a label.", p);
@@ -117,7 +154,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
r = errno ? -errno : -EIO;
return log_error_errno(r, "Failed to probe file system type \"%s\": %m", p);
}
-
if (!streq(v, "vfat")) {
log_error("File system \"%s\" is not FAT.", p);
return -ENODEV;
@@ -129,7 +165,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
r = errno ? -errno : -EIO;
return log_error_errno(r, "Failed to probe partition scheme \"%s\": %m", p);
}
-
if (!streq(v, "gpt")) {
log_error("File system \"%s\" is not on a GPT partition table.", p);
return -ENODEV;
@@ -141,7 +176,6 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
r = errno ? -errno : -EIO;
return log_error_errno(r, "Failed to probe partition type UUID \"%s\": %m", p);
}
-
if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) {
log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p);
return -ENODEV;
@@ -153,8 +187,7 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
r = errno ? -errno : -EIO;
return log_error_errno(r, "Failed to probe partition entry UUID \"%s\": %m", p);
}
-
- r = sd_id128_from_string(v, uuid);
+ r = sd_id128_from_string(v, &uuid);
if (r < 0) {
log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v);
return -EIO;
@@ -166,7 +199,9 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
r = errno ? -errno : -EIO;
return log_error_errno(r, "Failed to probe partition number \"%s\": m", p);
}
- *part = strtoul(v, NULL, 10);
+ r = safe_atou32(v, &part);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL);
@@ -174,7 +209,9 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
r = errno ? -errno : -EIO;
return log_error_errno(r, "Failed to probe partition offset \"%s\": %m", p);
}
- *pstart = strtoul(v, NULL, 10);
+ r = safe_atou64(v, &pstart);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
errno = 0;
r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL);
@@ -182,11 +219,50 @@ static int verify_esp(const char *p, uint32_t *part, uint64_t *pstart, uint64_t
r = errno ? -errno : -EIO;
return log_error_errno(r, "Failed to probe partition size \"%s\": %m", p);
}
- *psize = strtoul(v, NULL, 10);
+ r = safe_atou64(v, &psize);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
+
+finish:
+ if (ret_part)
+ *ret_part = part;
+ if (ret_pstart)
+ *ret_pstart = pstart;
+ if (ret_psize)
+ *ret_psize = psize;
+ if (ret_uuid)
+ *ret_uuid = uuid;
return 0;
}
+static int find_esp(uint32_t *part, uint64_t *pstart, uint64_t *psize, sd_id128_t *uuid) {
+ const char *path;
+ int r;
+
+ if (arg_path)
+ return verify_esp(false, arg_path, part, pstart, psize, uuid);
+
+ FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") {
+
+ r = verify_esp(true, path, part, pstart, psize, uuid);
+ if (IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
+ continue;
+ if (r < 0)
+ return r;
+
+ arg_path = strdup(path);
+ if (!arg_path)
+ return log_oom();
+
+ log_info("Using EFI System Parition at %s.", path);
+ return 0;
+ }
+
+ log_error("Couldn't find EFI system partition. It is recommended to mount it to /boot. Alternatively, use --path= to specify path to mount point.");
+ return -ENOENT;
+}
+
/* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */
static int get_file_version(int fd, char **v) {
struct stat st;
@@ -199,14 +275,16 @@ static int get_file_version(int fd, char **v) {
assert(v);
if (fstat(fd, &st) < 0)
- return -errno;
+ return log_error_errno(errno, "Failed to stat EFI binary: %m");
- if (st.st_size < 27)
+ if (st.st_size < 27) {
+ *v = NULL;
return 0;
+ }
buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (buf == MAP_FAILED)
- return -errno;
+ return log_error_errno(errno, "Failed to memory map EFI binary: %m");
s = memmem(buf, st.st_size - 8, "#### LoaderInfo: ", 17);
if (!s)
@@ -228,7 +306,7 @@ static int get_file_version(int fd, char **v) {
r = 1;
finish:
- munmap(buf, st.st_size);
+ (void) munmap(buf, st.st_size);
*v = x;
return r;
}
@@ -288,7 +366,7 @@ static int status_binaries(const char *esp_path, sd_id128_t partition) {
else if (r < 0)
return r;
- r = enumerate_binaries(esp_path, "EFI/Boot", "boot");
+ r = enumerate_binaries(esp_path, "EFI/BOOT", "boot");
if (r == 0)
log_error("No default/fallback boot loader installed in ESP.");
else if (r < 0)
@@ -311,7 +389,7 @@ static int print_efi_option(uint16_t id, bool in_order) {
return r;
/* print only configured entries with partition information */
- if (!path || sd_id128_equal(partition, SD_ID128_NULL))
+ if (!path || sd_id128_is_null(partition))
return 0;
efi_tilt_backslashes(path);
@@ -338,9 +416,10 @@ static int status_variables(void) {
n_options = efi_get_boot_options(&options);
if (n_options == -ENOENT)
- return log_error_errno(ENOENT, "Failed to access EFI variables, efivarfs"
+ return log_error_errno(n_options,
+ "Failed to access EFI variables, efivarfs"
" needs to be available at /sys/firmware/efi/efivars/.");
- else if (n_options < 0)
+ if (n_options < 0)
return log_error_errno(n_options, "Failed to read EFI boot entries: %m");
n_order = efi_get_boot_order(&order);
@@ -360,11 +439,9 @@ static int status_variables(void) {
for (j = 0; j < n_order; j++)
if (options[i] == order[j])
- goto next;
+ continue;
print_efi_option(options[i], false);
- next:
- continue;
}
return 0;
@@ -523,15 +600,6 @@ error:
return r;
}
-static char* strupper(char *s) {
- char *p;
-
- for (p = s; *p; p++)
- *p = toupper(*p);
-
- return s;
-}
-
static int mkdir_one(const char *prefix, const char *suffix) {
char *p;
@@ -548,17 +616,17 @@ static int mkdir_one(const char *prefix, const char *suffix) {
static const char *efi_subdirs[] = {
"EFI",
"EFI/systemd",
- "EFI/Boot",
+ "EFI/BOOT",
"loader",
"loader/entries"
};
static int create_dirs(const char *esp_path) {
+ const char **i;
int r;
- unsigned i;
- for (i = 0; i < ELEMENTSOF(efi_subdirs); i++) {
- r = mkdir_one(esp_path, efi_subdirs[i]);
+ STRV_FOREACH(i, efi_subdirs) {
+ r = mkdir_one(esp_path, *i);
if (r < 0)
return r;
}
@@ -579,8 +647,8 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) {
char *v;
/* Create the EFI default boot loader name (specified for removable devices) */
- v = strjoina(esp_path, "/EFI/Boot/BOOT", name + strlen("systemd-boot"));
- strupper(strrchr(v, '/') + 1);
+ v = strjoina(esp_path, "/EFI/BOOT/BOOT", name + strlen("systemd-boot"));
+ ascii_strupper(strrchr(v, '/') + 1);
k = copy_file(p, v, force);
if (k < 0 && r == 0)
@@ -751,8 +819,8 @@ static int install_variables(const char *esp_path,
if (access(p, F_OK) < 0) {
if (errno == ENOENT)
return 0;
- else
- return log_error_errno(errno, "Cannot access \"%s\": %m", p);
+
+ return log_error_errno(errno, "Cannot access \"%s\": %m", p);
}
r = find_slot(uuid, path, &slot);
@@ -762,7 +830,7 @@ static int install_variables(const char *esp_path,
"Failed to access EFI variables. Is the \"efivarfs\" filesystem mounted?" :
"Failed to determine current boot order: %m");
- if (first || r == false) {
+ if (first || r == 0) {
r = efi_add_boot_option(slot, "Linux Boot Manager",
part, pstart, psize,
uuid, path);
@@ -781,7 +849,7 @@ static int remove_boot_efi(const char *esp_path) {
struct dirent *de;
int r, c = 0;
- p = strjoina(esp_path, "/EFI/Boot");
+ p = strjoina(esp_path, "/EFI/BOOT");
d = opendir(p);
if (!d) {
if (errno == ENOENT)
@@ -797,7 +865,7 @@ static int remove_boot_efi(const char *esp_path) {
if (!endswith_no_case(de->d_name, ".efi"))
continue;
- if (!startswith_no_case(de->d_name, "Boot"))
+ if (!startswith_no_case(de->d_name, "boot"))
continue;
fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC);
@@ -872,46 +940,39 @@ static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) {
if (in_order)
return remove_from_order(slot);
- else
- return 0;
+
+ return 0;
}
static int install_loader_config(const char *esp_path) {
- char *p;
- char line[64];
- char *machine = NULL;
- _cleanup_fclose_ FILE *f = NULL, *g = NULL;
- f = fopen("/etc/machine-id", "re");
- if (!f)
- return errno == ENOENT ? 0 : -errno;
+ _cleanup_fclose_ FILE *f = NULL;
+ char machine_string[SD_ID128_STRING_MAX];
+ sd_id128_t machine_id;
+ const char *p;
+ int r;
- if (fgets(line, sizeof(line), f) != NULL) {
- char *s;
+ r = sd_id128_get_machine(&machine_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get machine did: %m");
- s = strchr(line, '\n');
- if (s)
- s[0] = '\0';
- if (strlen(line) == 32)
- machine = line;
- }
+ p = strjoina(esp_path, "/loader/loader.conf");
+ f = fopen(p, "wxe");
+ if (!f)
+ return log_error_errno(errno, "Failed to open loader.conf for writing: %m");
- if (!machine)
- return -ESRCH;
+ fprintf(f, "#timeout 3\n");
+ fprintf(f, "default %s-*\n", sd_id128_to_string(machine_id, machine_string));
- p = strjoina(esp_path, "/loader/loader.conf");
- g = fopen(p, "wxe");
- if (g) {
- fprintf(g, "#timeout 3\n");
- fprintf(g, "default %s-*\n", machine);
- if (ferror(g))
- return log_error_errno(EIO, "Failed to write \"%s\": %m", p);
- }
+ r = fflush_and_check(f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write \"%s\": %m", p);
return 0;
}
-static int help(void) {
+static int help(int argc, char *argv[], void *userdata) {
+
printf("%s [COMMAND] [OPTIONS...]\n"
"\n"
"Install, update or remove the systemd-boot EFI boot manager.\n\n"
@@ -930,9 +991,6 @@ static int help(void) {
return 0;
}
-static const char *arg_path = "/boot";
-static bool arg_touch_variables = true;
-
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_PATH = 0x100,
@@ -948,7 +1006,7 @@ static int parse_argv(int argc, char *argv[]) {
{ NULL, 0, NULL, 0 }
};
- int c;
+ int c, r;
assert(argc >= 0);
assert(argv);
@@ -957,14 +1015,16 @@ static int parse_argv(int argc, char *argv[]) {
switch (c) {
case 'h':
- help();
+ help(0, NULL, NULL);
return 0;
case ARG_VERSION:
return version();
case ARG_PATH:
- arg_path = optarg;
+ r = free_and_strdup(&arg_path, optarg);
+ if (r < 0)
+ return log_oom();
break;
case ARG_NO_VARIABLES:
@@ -989,149 +1049,170 @@ static void read_loader_efi_var(const char *name, char **var) {
log_warning_errno(r, "Failed to read EFI variable %s: %m", name);
}
-static int bootctl_main(int argc, char*argv[]) {
- enum action {
- ACTION_STATUS,
- ACTION_INSTALL,
- ACTION_UPDATE,
- ACTION_REMOVE
- } arg_action = ACTION_STATUS;
- static const struct {
- const char* verb;
- enum action action;
- } verbs[] = {
- { "status", ACTION_STATUS },
- { "install", ACTION_INSTALL },
- { "update", ACTION_UPDATE },
- { "remove", ACTION_REMOVE },
- };
+static int must_be_root(void) {
- sd_id128_t uuid = {};
- uint32_t part = 0;
- uint64_t pstart = 0, psize = 0;
- int r, q;
+ if (geteuid() == 0)
+ return 0;
- if (argv[optind]) {
- unsigned i;
+ log_error("Need to be root.");
+ return -EPERM;
+}
- for (i = 0; i < ELEMENTSOF(verbs); i++) {
- if (!streq(argv[optind], verbs[i].verb))
- continue;
- arg_action = verbs[i].action;
- break;
- }
- if (i >= ELEMENTSOF(verbs)) {
- log_error("Unknown operation \"%s\"", argv[optind]);
- return -EINVAL;
- }
- }
+static int verb_status(int argc, char *argv[], void *userdata) {
- if (geteuid() != 0)
- return log_error_errno(EPERM, "Need to be root.");
+ sd_id128_t uuid = SD_ID128_NULL;
+ int r;
- r = verify_esp(arg_path, &part, &pstart, &psize, &uuid);
- if (r == -ENODEV && !arg_path)
- log_notice("You might want to use --path= to indicate the path to your ESP, in case it is not mounted on /boot.");
+ r = must_be_root();
if (r < 0)
return r;
- switch (arg_action) {
- case ACTION_STATUS: {
- _cleanup_free_ char *fw_type = NULL;
- _cleanup_free_ char *fw_info = NULL;
- _cleanup_free_ char *loader = NULL;
- _cleanup_free_ char *loader_path = NULL;
- sd_id128_t loader_part_uuid = {};
-
- if (is_efi_boot()) {
- read_loader_efi_var("LoaderFirmwareType", &fw_type);
- read_loader_efi_var("LoaderFirmwareInfo", &fw_info);
- read_loader_efi_var("LoaderInfo", &loader);
- read_loader_efi_var("LoaderImageIdentifier", &loader_path);
- if (loader_path)
- efi_tilt_backslashes(loader_path);
- r = efi_loader_get_device_part_uuid(&loader_part_uuid);
- if (r < 0 && r == -ENOENT)
- log_warning_errno(r, "Failed to read EFI variable LoaderDevicePartUUID: %m");
-
- printf("System:\n");
- printf(" Firmware: %s (%s)\n", strna(fw_type), strna(fw_info));
-
- r = is_efi_secure_boot();
- if (r < 0)
- log_warning_errno(r, "Failed to query secure boot status: %m");
- else
- printf(" Secure Boot: %s\n", r ? "enabled" : "disabled");
+ r = find_esp(NULL, NULL, NULL, &uuid);
+ if (r < 0)
+ return r;
- r = is_efi_secure_boot_setup_mode();
- if (r < 0)
- log_warning_errno(r, "Failed to query secure boot mode: %m");
- else
- printf(" Setup Mode: %s\n", r ? "setup" : "user");
- printf("\n");
-
- printf("Loader:\n");
- printf(" Product: %s\n", strna(loader));
- if (!sd_id128_equal(loader_part_uuid, SD_ID128_NULL))
- printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
- SD_ID128_FORMAT_VAL(loader_part_uuid));
- else
- printf(" Partition: n/a\n");
- printf(" File: %s%s\n", special_glyph(TREE_RIGHT), strna(loader_path));
- printf("\n");
- } else
- printf("System:\n Not booted with EFI\n");
-
- r = status_binaries(arg_path, uuid);
+ if (is_efi_boot()) {
+ _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL;
+ sd_id128_t loader_part_uuid = SD_ID128_NULL;
+
+ read_loader_efi_var("LoaderFirmwareType", &fw_type);
+ read_loader_efi_var("LoaderFirmwareInfo", &fw_info);
+ read_loader_efi_var("LoaderInfo", &loader);
+ read_loader_efi_var("LoaderImageIdentifier", &loader_path);
+
+ if (loader_path)
+ efi_tilt_backslashes(loader_path);
+
+ r = efi_loader_get_device_part_uuid(&loader_part_uuid);
+ if (r < 0 && r != -ENOENT)
+ log_warning_errno(r, "Failed to read EFI variable LoaderDevicePartUUID: %m");
+
+ printf("System:\n");
+ printf(" Firmware: %s (%s)\n", strna(fw_type), strna(fw_info));
+
+ r = is_efi_secure_boot();
if (r < 0)
- return r;
+ log_warning_errno(r, "Failed to query secure boot status: %m");
+ else
+ printf(" Secure Boot: %sd\n", enable_disable(r));
- if (arg_touch_variables)
- r = status_variables();
- break;
- }
+ r = is_efi_secure_boot_setup_mode();
+ if (r < 0)
+ log_warning_errno(r, "Failed to query secure boot mode: %m");
+ else
+ printf(" Setup Mode: %s\n", r ? "setup" : "user");
+ printf("\n");
+
+ printf("Loader:\n");
+ printf(" Product: %s\n", strna(loader));
+ if (!sd_id128_is_null(loader_part_uuid))
+ printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
+ SD_ID128_FORMAT_VAL(loader_part_uuid));
+ else
+ printf(" Partition: n/a\n");
+ printf(" File: %s%s\n", special_glyph(TREE_RIGHT), strna(loader_path));
+ printf("\n");
+ } else
+ printf("System:\n Not booted with EFI\n");
- case ACTION_INSTALL:
- case ACTION_UPDATE:
- umask(0002);
+ r = status_binaries(arg_path, uuid);
+ if (r < 0)
+ return r;
+
+ if (arg_touch_variables)
+ r = status_variables();
- r = install_binaries(arg_path, arg_action == ACTION_INSTALL);
+ return r;
+}
+
+static int verb_install(int argc, char *argv[], void *userdata) {
+
+ sd_id128_t uuid = SD_ID128_NULL;
+ uint64_t pstart = 0, psize = 0;
+ uint32_t part = 0;
+ bool install;
+ int r;
+
+ r = must_be_root();
+ if (r < 0)
+ return r;
+
+ r = find_esp(&part, &pstart, &psize, &uuid);
+ if (r < 0)
+ return r;
+
+ install = streq(argv[0], "install");
+
+ RUN_WITH_UMASK(0002) {
+ r = install_binaries(arg_path, install);
if (r < 0)
return r;
- if (arg_action == ACTION_INSTALL) {
+ if (install) {
r = install_loader_config(arg_path);
if (r < 0)
return r;
}
+ }
- if (arg_touch_variables)
- r = install_variables(arg_path,
- part, pstart, psize, uuid,
- "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi",
- arg_action == ACTION_INSTALL);
- break;
+ if (arg_touch_variables)
+ r = install_variables(arg_path,
+ part, pstart, psize, uuid,
+ "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi",
+ install);
- case ACTION_REMOVE:
- r = remove_binaries(arg_path);
+ return r;
+}
- if (arg_touch_variables) {
- q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true);
- if (q < 0 && r == 0)
- r = q;
- }
- break;
+static int verb_remove(int argc, char *argv[], void *userdata) {
+ sd_id128_t uuid = SD_ID128_NULL;
+ int r;
+
+ r = must_be_root();
+ if (r < 0)
+ return r;
+
+ r = find_esp(NULL, NULL, NULL, &uuid);
+ if (r < 0)
+ return r;
+
+ r = remove_binaries(arg_path);
+
+ if (arg_touch_variables) {
+ int q;
+
+ q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true);
+ if (q < 0 && r == 0)
+ r = q;
}
return r;
}
+static int bootctl_main(int argc, char *argv[]) {
+
+ static const Verb verbs[] = {
+ { "help", VERB_ANY, VERB_ANY, 0, help },
+ { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
+ { "install", VERB_ANY, 1, 0, verb_install },
+ { "update", VERB_ANY, 1, 0, verb_install },
+ { "remove", VERB_ANY, 1, 0, verb_remove },
+ {}
+ };
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
int main(int argc, char *argv[]) {
int r;
log_parse_environment();
log_open();
+ /* If we run in a container, automatically turn of EFI file system access */
+ if (detect_container() > 0)
+ arg_touch_variables = false;
+
r = parse_argv(argc, argv);
if (r <= 0)
goto finish;
@@ -1139,5 +1220,6 @@ int main(int argc, char *argv[]) {
r = bootctl_main(argc, argv);
finish:
+ free(arg_path);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
diff --git a/src/boot/efi/console.c b/src/boot/efi/console.c
index c436f8b476..2b797c9a5f 100644
--- a/src/boot/efi/console.c
+++ b/src/boot/efi/console.c
@@ -93,12 +93,8 @@ EFI_STATUS console_key_read(UINT64 *key, BOOLEAN wait) {
}
/* wait until key is pressed */
- if (wait) {
- if (TextInputEx)
- uefi_call_wrapper(BS->WaitForEvent, 3, 1, &TextInputEx->WaitForKeyEx, &index);
- else
- uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
- }
+ if (wait)
+ uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
if (TextInputEx) {
EFI_KEY_DATA keydata;
diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c
index b4a982ce38..c67b328b38 100644
--- a/src/cgtop/cgtop.c
+++ b/src/cgtop/cgtop.c
@@ -872,9 +872,13 @@ static int get_cgroup_root(char **ret) {
int r;
if (arg_root) {
- *ret = strdup(arg_root);
- if (!*ret)
+ char *aux;
+
+ aux = strdup(arg_root);
+ if (!aux)
return log_oom();
+
+ *ret = aux;
return 0;
}
diff --git a/src/core/automount.c b/src/core/automount.c
index 10ea4ee17d..20a73c76f9 100644
--- a/src/core/automount.c
+++ b/src/core/automount.c
@@ -98,9 +98,6 @@ static void unmount_autofs(Automount *a) {
if (a->pipe_fd < 0)
return;
- automount_send_ready(a, a->tokens, -EHOSTDOWN);
- automount_send_ready(a, a->expire_tokens, -EHOSTDOWN);
-
a->pipe_event_source = sd_event_source_unref(a->pipe_event_source);
a->pipe_fd = safe_close(a->pipe_fd);
@@ -109,6 +106,9 @@ static void unmount_autofs(Automount *a) {
if (a->where &&
(UNIT(a)->manager->exit_code != MANAGER_RELOAD &&
UNIT(a)->manager->exit_code != MANAGER_REEXECUTE)) {
+ automount_send_ready(a, a->tokens, -EHOSTDOWN);
+ automount_send_ready(a, a->expire_tokens, -EHOSTDOWN);
+
r = repeat_unmount(a->where, MNT_DETACH);
if (r < 0)
log_error_errno(r, "Failed to unmount: %m");
diff --git a/src/core/busname.c b/src/core/busname.c
index f03a95c24e..730be2ee14 100644
--- a/src/core/busname.c
+++ b/src/core/busname.c
@@ -998,12 +998,7 @@ static int busname_get_timeout(Unit *u, usec_t *timeout) {
}
static bool busname_supported(void) {
- static int supported = -1;
-
- if (supported < 0)
- supported = is_kdbus_available();
-
- return supported;
+ return false;
}
static int busname_control_pid(Unit *u) {
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
index f3e0c54b76..c19e43f571 100644
--- a/src/core/cgroup.c
+++ b/src/core/cgroup.c
@@ -36,8 +36,7 @@
#define CGROUP_CPU_QUOTA_PERIOD_USEC ((usec_t) 100 * USEC_PER_MSEC)
-static void cgroup_compat_warn(void)
-{
+static void cgroup_compat_warn(void) {
static bool cgroup_compat_warned = false;
if (cgroup_compat_warned)
@@ -50,7 +49,7 @@ static void cgroup_compat_warn(void)
#define log_cgroup_compat(unit, fmt, ...) do { \
cgroup_compat_warn(); \
log_unit_debug(unit, "cgroup-compat: " fmt, ##__VA_ARGS__); \
- } while (0)
+ } while (false)
void cgroup_context_init(CGroupContext *c) {
assert(c);
@@ -756,16 +755,20 @@ static void cgroup_context_apply(Unit *u, CGroupMask mask, ManagerState state) {
cgroup_apply_unified_memory_limit(u, "memory.max", max);
} else {
char buf[DECIMAL_STR_MAX(uint64_t) + 1];
+ uint64_t val = c->memory_limit;
- if (c->memory_limit != CGROUP_LIMIT_MAX)
- xsprintf(buf, "%" PRIu64 "\n", c->memory_limit);
- else {
- xsprintf(buf, "%" PRIu64 "\n", c->memory_max);
+ if (val == CGROUP_LIMIT_MAX) {
+ val = c->memory_max;
- if (c->memory_max != CGROUP_LIMIT_MAX)
- log_cgroup_compat(u, "Applying MemoryMax %" PRIu64 " as MemoryLimit", c->memory_max);
+ if (val != CGROUP_LIMIT_MAX)
+ log_cgroup_compat(u, "Applying MemoryMax %" PRIi64 " as MemoryLimit", c->memory_max);
}
+ if (val == CGROUP_LIMIT_MAX)
+ strncpy(buf, "-1\n", sizeof(buf));
+ else
+ xsprintf(buf, "%" PRIu64 "\n", val);
+
r = cg_set_attribute("memory", path, "memory.limit_in_bytes", buf);
if (r < 0)
log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
@@ -797,7 +800,10 @@ static void cgroup_context_apply(Unit *u, CGroupMask mask, ManagerState state) {
"/dev/random\0" "rwm\0"
"/dev/urandom\0" "rwm\0"
"/dev/tty\0" "rwm\0"
- "/dev/pts/ptmx\0" "rw\0"; /* /dev/pts/ptmx may not be duplicated, but accessed */
+ "/dev/pts/ptmx\0" "rw\0" /* /dev/pts/ptmx may not be duplicated, but accessed */
+ /* Allow /run/systemd/inaccessible/{chr,blk} devices for mapping InaccessiblePaths */
+ "/run/systemd/inaccessible/chr\0" "rwm\0"
+ "/run/systemd/inaccessible/blk\0" "rwm\0";
const char *x, *y;
@@ -1133,7 +1139,7 @@ int unit_watch_cgroup(Unit *u) {
/* Only applies to the unified hierarchy */
r = cg_unified();
if (r < 0)
- return log_unit_error_errno(u, r, "Failed detect wether the unified hierarchy is used: %m");
+ return log_unit_error_errno(u, r, "Failed detect whether the unified hierarchy is used: %m");
if (r == 0)
return 0;
@@ -1655,7 +1661,7 @@ int manager_setup_cgroup(Manager *m) {
/* 3. Install agent */
if (unified) {
- /* In the unified hierarchy we can can get
+ /* In the unified hierarchy we can get
* cgroup empty notifications via inotify. */
m->cgroup_inotify_event_source = sd_event_source_unref(m->cgroup_inotify_event_source);
@@ -1702,7 +1708,7 @@ int manager_setup_cgroup(Manager *m) {
/* also, move all other userspace processes remaining
* in the root cgroup into that scope. */
- r = cg_migrate(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, SYSTEMD_CGROUP_CONTROLLER, scope_path, false);
+ r = cg_migrate(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, SYSTEMD_CGROUP_CONTROLLER, scope_path, 0);
if (r < 0)
log_warning_errno(r, "Couldn't move remaining userspace processes, ignoring: %m");
@@ -1723,7 +1729,7 @@ int manager_setup_cgroup(Manager *m) {
return log_error_errno(r, "Failed to determine supported controllers: %m");
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++)
- log_debug("Controller '%s' supported: %s", cgroup_controller_to_string(c), yes_no(m->cgroup_supported & c));
+ log_debug("Controller '%s' supported: %s", cgroup_controller_to_string(c), yes_no(m->cgroup_supported & CGROUP_CONTROLLER_TO_MASK(c)));
return 0;
}
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
index f21409bd5d..a57403e79f 100644
--- a/src/core/cgroup.h
+++ b/src/core/cgroup.h
@@ -119,7 +119,6 @@ struct CGroupContext {
bool delegate;
};
-#include "cgroup-util.h"
#include "unit.h"
void cgroup_context_init(CGroupContext *c);
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
index 8525fa1bf1..85b0c86a2f 100644
--- a/src/core/dbus-cgroup.c
+++ b/src/core/dbus-cgroup.c
@@ -641,7 +641,7 @@ int bus_cgroup_set_property(
return 1;
- } else if (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth")) {
+ } else if (STR_IN_SET(name, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) {
const char *path;
bool read = true;
unsigned n = 0;
@@ -835,6 +835,8 @@ int bus_cgroup_set_property(
r = sd_bus_message_read(message, "t", &v);
if (r < 0)
return r;
+ if (v <= 0)
+ return sd_bus_error_set_errnof(error, EINVAL, "%s= is too small", name);
if (mode != UNIT_CHECK) {
if (streq(name, "MemoryLow"))
@@ -847,19 +849,54 @@ int bus_cgroup_set_property(
unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY);
if (v == CGROUP_LIMIT_MAX)
- unit_write_drop_in_private_format(u, mode, name, "%s=max", name);
+ unit_write_drop_in_private_format(u, mode, name, "%s=infinity", name);
else
unit_write_drop_in_private_format(u, mode, name, "%s=%" PRIu64, name, v);
}
return 1;
+ } else if (STR_IN_SET(name, "MemoryLowScale", "MemoryHighScale", "MemoryMaxScale")) {
+ uint32_t raw;
+ uint64_t v;
+
+ r = sd_bus_message_read(message, "u", &raw);
+ if (r < 0)
+ return r;
+
+ v = physical_memory_scale(raw, UINT32_MAX);
+ if (v <= 0 || v == UINT64_MAX)
+ return sd_bus_error_set_errnof(error, EINVAL, "%s= is out of range", name);
+
+ if (mode != UNIT_CHECK) {
+ const char *e;
+
+ /* Chop off suffix */
+ assert_se(e = endswith(name, "Scale"));
+ name = strndupa(name, e - name);
+
+ if (streq(name, "MemoryLow"))
+ c->memory_low = v;
+ else if (streq(name, "MemoryHigh"))
+ c->memory_high = v;
+ else
+ c->memory_max = v;
+
+ unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY);
+ unit_write_drop_in_private_format(u, mode, name, "%s=%" PRIu32 "%%", name,
+ (uint32_t) (DIV_ROUND_UP((uint64_t) raw * 100U, (uint64_t) UINT32_MAX)));
+ }
+
+ return 1;
+
} else if (streq(name, "MemoryLimit")) {
uint64_t limit;
r = sd_bus_message_read(message, "t", &limit);
if (r < 0)
return r;
+ if (limit <= 0)
+ return sd_bus_error_set_errnof(error, EINVAL, "%s= is too small", name);
if (mode != UNIT_CHECK) {
c->memory_limit = limit;
@@ -873,6 +910,27 @@ int bus_cgroup_set_property(
return 1;
+ } else if (streq(name, "MemoryLimitScale")) {
+ uint64_t limit;
+ uint32_t raw;
+
+ r = sd_bus_message_read(message, "u", &raw);
+ if (r < 0)
+ return r;
+
+ limit = physical_memory_scale(raw, UINT32_MAX);
+ if (limit <= 0 || limit == UINT64_MAX)
+ return sd_bus_error_set_errnof(error, EINVAL, "%s= is out of range", name);
+
+ if (mode != UNIT_CHECK) {
+ c->memory_limit = limit;
+ unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY);
+ unit_write_drop_in_private_format(u, mode, "MemoryLimit", "MemoryLimit=%" PRIu32 "%%",
+ (uint32_t) (DIV_ROUND_UP((uint64_t) raw * 100U, (uint64_t) UINT32_MAX)));
+ }
+
+ return 1;
+
} else if (streq(name, "DevicePolicy")) {
const char *policy;
CGroupDevicePolicy p;
@@ -904,6 +962,7 @@ int bus_cgroup_set_property(
while ((r = sd_bus_message_read(message, "(ss)", &path, &rwm)) > 0) {
if ((!startswith(path, "/dev/") &&
+ !startswith(path, "/run/systemd/inaccessible/") &&
!startswith(path, "block-") &&
!startswith(path, "char-")) ||
strpbrk(path, WHITESPACE))
@@ -1003,6 +1062,8 @@ int bus_cgroup_set_property(
r = sd_bus_message_read(message, "t", &limit);
if (r < 0)
return r;
+ if (limit <= 0)
+ return sd_bus_error_set_errnof(error, EINVAL, "%s= is too small", name);
if (mode != UNIT_CHECK) {
c->tasks_max = limit;
@@ -1015,6 +1076,26 @@ int bus_cgroup_set_property(
}
return 1;
+ } else if (streq(name, "TasksMaxScale")) {
+ uint64_t limit;
+ uint32_t raw;
+
+ r = sd_bus_message_read(message, "u", &raw);
+ if (r < 0)
+ return r;
+
+ limit = system_tasks_max_scale(raw, UINT32_MAX);
+ if (limit <= 0 || limit >= UINT64_MAX)
+ return sd_bus_error_set_errnof(error, EINVAL, "%s= is out of range", name);
+
+ if (mode != UNIT_CHECK) {
+ c->tasks_max = limit;
+ unit_invalidate_cgroup(u, CGROUP_MASK_PIDS);
+ unit_write_drop_in_private_format(u, mode, name, "TasksMax=%" PRIu32 "%%",
+ (uint32_t) (DIV_ROUND_UP((uint64_t) raw * 100U, (uint64_t) UINT32_MAX)));
+ }
+
+ return 1;
}
if (u->transient && u->load_state == UNIT_STUB) {
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index 4c88c41127..9c50cd93e5 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -44,6 +44,7 @@
#endif
#include "strv.h"
#include "syslog-util.h"
+#include "user-util.h"
#include "utf8.h"
BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput);
@@ -693,11 +694,15 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("ReadWriteDirectories", "as", NULL, offsetof(ExecContext, read_write_dirs), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("ReadOnlyDirectories", "as", NULL, offsetof(ExecContext, read_only_dirs), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("InaccessibleDirectories", "as", NULL, offsetof(ExecContext, inaccessible_dirs), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("ReadWriteDirectories", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
+ SD_BUS_PROPERTY("ReadOnlyDirectories", "as", NULL, offsetof(ExecContext, read_only_paths), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
+ SD_BUS_PROPERTY("InaccessibleDirectories", "as", NULL, offsetof(ExecContext, inaccessible_paths), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
+ SD_BUS_PROPERTY("ReadWritePaths", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("ReadOnlyPaths", "as", NULL, offsetof(ExecContext, read_only_paths), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("InaccessiblePaths", "as", NULL, offsetof(ExecContext, inaccessible_paths), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MountFlags", "t", bus_property_get_ulong, offsetof(ExecContext, mount_flags), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateTmp", "b", bus_property_get_bool, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateNetwork", "b", bus_property_get_bool, offsetof(ExecContext, private_network), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -720,6 +725,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, runtime_directory_mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectory", "as", NULL, offsetof(ExecContext, runtime_directory), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MemoryDenyWriteExecute", "b", bus_property_get_bool, offsetof(ExecContext, memory_deny_write_execute), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RestrictRealtime", "b", bus_property_get_bool, offsetof(ExecContext, restrict_realtime), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_VTABLE_END
};
@@ -836,6 +842,9 @@ int bus_exec_context_set_transient_property(
if (r < 0)
return r;
+ if (!isempty(uu) && !valid_user_group_name_or_id(uu))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user name: %s", uu);
+
if (mode != UNIT_CHECK) {
if (isempty(uu))
@@ -855,6 +864,9 @@ int bus_exec_context_set_transient_property(
if (r < 0)
return r;
+ if (!isempty(gg) && !valid_user_group_name_or_id(gg))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group name: %s", gg);
+
if (mode != UNIT_CHECK) {
if (isempty(gg))
@@ -1057,7 +1069,8 @@ int bus_exec_context_set_transient_property(
} else if (STR_IN_SET(name,
"IgnoreSIGPIPE", "TTYVHangup", "TTYReset",
"PrivateTmp", "PrivateDevices", "PrivateNetwork",
- "NoNewPrivileges", "SyslogLevelPrefix", "MemoryDenyWriteExecute")) {
+ "NoNewPrivileges", "SyslogLevelPrefix", "MemoryDenyWriteExecute",
+ "RestrictRealtime", "DynamicUser")) {
int b;
r = sd_bus_message_read(message, "b", &b);
@@ -1083,6 +1096,10 @@ int bus_exec_context_set_transient_property(
c->syslog_level_prefix = b;
else if (streq(name, "MemoryDenyWriteExecute"))
c->memory_deny_write_execute = b;
+ else if (streq(name, "RestrictRealtime"))
+ c->restrict_realtime = b;
+ else if (streq(name, "DynamicUser"))
+ c->dynamic_user = b;
unit_write_drop_in_private_format(u, mode, name, "%s=%s", name, yes_no(b));
}
@@ -1320,8 +1337,8 @@ int bus_exec_context_set_transient_property(
return 1;
- } else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) {
-
+ } else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
+ "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
_cleanup_strv_free_ char **l = NULL;
char ***dirs;
char **p;
@@ -1343,12 +1360,12 @@ int bus_exec_context_set_transient_property(
if (mode != UNIT_CHECK) {
_cleanup_free_ char *joined = NULL;
- if (streq(name, "ReadWriteDirectories"))
- dirs = &c->read_write_dirs;
- else if (streq(name, "ReadOnlyDirectories"))
- dirs = &c->read_only_dirs;
- else /* "InaccessibleDirectories" */
- dirs = &c->inaccessible_dirs;
+ if (STR_IN_SET(name, "ReadWriteDirectories", "ReadWritePaths"))
+ dirs = &c->read_write_paths;
+ else if (STR_IN_SET(name, "ReadOnlyDirectories", "ReadOnlyPaths"))
+ dirs = &c->read_only_paths;
+ else /* "InaccessiblePaths" */
+ dirs = &c->inaccessible_paths;
if (strv_length(l) == 0) {
*dirs = strv_free(*dirs);
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index 86722e1162..ef05a75a8b 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -43,6 +43,7 @@
#include "string-util.h"
#include "strv.h"
#include "syslog-util.h"
+#include "user-util.h"
#include "virt.h"
#include "watchdog.h"
@@ -781,6 +782,7 @@ static int transient_unit_from_message(
return r;
/* Now load the missing bits of the unit we just created */
+ unit_add_to_load_queue(u);
manager_dispatch_load_queue(m);
*unit = u;
@@ -1510,8 +1512,8 @@ static int method_unset_and_set_environment(sd_bus_message *message, void *userd
}
static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_error *error) {
- uint8_t code;
Manager *m = userdata;
+ uint8_t code;
int r;
assert(message);
@@ -1533,6 +1535,61 @@ static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_
return sd_bus_reply_method_return(message, NULL);
}
+static int method_lookup_dynamic_user_by_name(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+ const char *name;
+ uid_t uid;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read_basic(message, 's', &name);
+ if (r < 0)
+ return r;
+
+ if (!MANAGER_IS_SYSTEM(m))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance.");
+ if (!valid_user_group_name(name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name invalid: %s", name);
+
+ r = dynamic_user_lookup_name(m, name, &uid);
+ if (r == -ESRCH)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DYNAMIC_USER, "Dynamic user %s does not exist.", name);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, "u", (uint32_t) uid);
+}
+
+static int method_lookup_dynamic_user_by_uid(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ _cleanup_free_ char *name = NULL;
+ Manager *m = userdata;
+ uid_t uid;
+ int r;
+
+ assert(message);
+ assert(m);
+
+ assert_cc(sizeof(uid) == sizeof(uint32_t));
+ r = sd_bus_message_read_basic(message, 'u', &uid);
+ if (r < 0)
+ return r;
+
+ if (!MANAGER_IS_SYSTEM(m))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Dynamic users are only supported in the system instance.");
+ if (!uid_is_valid(uid))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User ID invalid: " UID_FMT, uid);
+
+ r = dynamic_user_lookup_uid(m, uid, &name);
+ if (r == -ESRCH)
+ return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_DYNAMIC_USER, "Dynamic user ID " UID_FMT " does not exist.", uid);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, "s", name);
+}
+
static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states, char **patterns) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
Manager *m = userdata;
@@ -2198,6 +2255,8 @@ const sd_bus_vtable bus_manager_vtable[] = {
SD_BUS_METHOD("PresetAllUnitFiles", "sbb", "a(sss)", method_preset_all_unit_files, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("AddDependencyUnitFiles", "asssbb", "a(sss)", method_add_dependency_unit_files, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetExitCode", "y", NULL, method_set_exit_code, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("LookupDynamicUserByName", "s", "u", method_lookup_dynamic_user_by_name, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("LookupDynamicUserByUID", "u", "s", method_lookup_dynamic_user_by_uid, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL("UnitNew", "so", 0),
SD_BUS_SIGNAL("UnitRemoved", "so", 0),
diff --git a/src/core/dbus-scope.c b/src/core/dbus-scope.c
index f557eedfc3..1abaf9f658 100644
--- a/src/core/dbus-scope.c
+++ b/src/core/dbus-scope.c
@@ -225,5 +225,5 @@ int bus_scope_send_request_stop(Scope *s) {
if (r < 0)
return r;
- return sd_bus_send_to(UNIT(s)->manager->api_bus, m, /* s->controller */ NULL, NULL);
+ return sd_bus_send_to(UNIT(s)->manager->api_bus, m, s->controller, NULL);
}
diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c
index 961340608d..9a071a1355 100644
--- a/src/core/dbus-socket.c
+++ b/src/core/dbus-socket.c
@@ -137,6 +137,7 @@ const sd_bus_vtable bus_socket_vtable[] = {
SD_BUS_PROPERTY("Symlinks", "as", NULL, offsetof(Socket, symlinks), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Mark", "i", bus_property_get_int, offsetof(Socket, mark), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MaxConnections", "u", bus_property_get_unsigned, offsetof(Socket, max_connections), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("MaxConnectionsPerSource", "u", bus_property_get_unsigned, offsetof(Socket, max_connections_per_source), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MessageQueueMaxMessages", "x", bus_property_get_long, offsetof(Socket, mq_maxmsg), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MessageQueueMessageSize", "x", bus_property_get_long, offsetof(Socket, mq_msgsize), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ReusePort", "b", bus_property_get_bool, offsetof(Socket, reuse_port), SD_BUS_VTABLE_PROPERTY_CONST),
diff --git a/src/core/dynamic-user.c b/src/core/dynamic-user.c
new file mode 100644
index 0000000000..8035bee231
--- /dev/null
+++ b/src/core/dynamic-user.c
@@ -0,0 +1,763 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <grp.h>
+#include <pwd.h>
+#include <sys/file.h>
+
+#include "dynamic-user.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "parse-util.h"
+#include "random-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "user-util.h"
+#include "fileio.h"
+
+/* Let's pick a UIDs within the 16bit range, so that we are compatible with containers using 16bit user namespacing. At
+ * least on Fedora normal users are allocated until UID 60000, hence do not allocate from below this. Also stay away
+ * from the upper end of the range as that is often used for overflow/nobody users. */
+#define UID_PICK_MIN ((uid_t) UINT32_C(0x0000EF00))
+#define UID_PICK_MAX ((uid_t) UINT32_C(0x0000FFEF))
+
+/* Takes a value generated randomly or by hashing and turns it into a UID in the right range */
+#define UID_CLAMP_INTO_RANGE(rnd) (((uid_t) (rnd) % (UID_PICK_MAX - UID_PICK_MIN + 1)) + UID_PICK_MIN)
+
+static DynamicUser* dynamic_user_free(DynamicUser *d) {
+ if (!d)
+ return NULL;
+
+ if (d->manager)
+ (void) hashmap_remove(d->manager->dynamic_users, d->name);
+
+ safe_close_pair(d->storage_socket);
+ free(d);
+
+ return NULL;
+}
+
+static int dynamic_user_add(Manager *m, const char *name, int storage_socket[2], DynamicUser **ret) {
+ DynamicUser *d = NULL;
+ int r;
+
+ assert(m);
+ assert(name);
+ assert(storage_socket);
+
+ r = hashmap_ensure_allocated(&m->dynamic_users, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ d = malloc0(offsetof(DynamicUser, name) + strlen(name) + 1);
+ if (!d)
+ return -ENOMEM;
+
+ strcpy(d->name, name);
+
+ d->storage_socket[0] = storage_socket[0];
+ d->storage_socket[1] = storage_socket[1];
+
+ r = hashmap_put(m->dynamic_users, d->name, d);
+ if (r < 0) {
+ free(d);
+ return r;
+ }
+
+ d->manager = m;
+
+ if (ret)
+ *ret = d;
+
+ return 0;
+}
+
+int dynamic_user_acquire(Manager *m, const char *name, DynamicUser** ret) {
+ _cleanup_close_pair_ int storage_socket[2] = { -1, -1 };
+ DynamicUser *d;
+ int r;
+
+ assert(m);
+ assert(name);
+
+ /* Return the DynamicUser structure for a specific user name. Note that this won't actually allocate a UID for
+ * it, but just prepare the data structure for it. The UID is allocated only on demand, when it's really
+ * needed, and in the child process we fork off, since allocation involves NSS checks which are not OK to do
+ * from PID 1. To allow the children and PID 1 share information about allocated UIDs we use an anonymous
+ * AF_UNIX/SOCK_DGRAM socket (called the "storage socket") that contains at most one datagram with the
+ * allocated UID number, plus an fd referencing the lock file for the UID
+ * (i.e. /run/systemd/dynamic-uid/$UID). Why involve the socket pair? So that PID 1 and all its children can
+ * share the same storage for the UID and lock fd, simply by inheriting the storage socket fds. The socket pair
+ * may exist in three different states:
+ *
+ * a) no datagram stored. This is the initial state. In this case the dynamic user was never realized.
+ *
+ * b) a datagram containing a UID stored, but no lock fd attached to it. In this case there was already a
+ * statically assigned UID by the same name, which we are reusing.
+ *
+ * c) a datagram containing a UID stored, and a lock fd is attached to it. In this case we allocated a dynamic
+ * UID and locked it in the file system, using the lock fd.
+ *
+ * As PID 1 and various children might access the socket pair simultaneously, and pop the datagram or push it
+ * back in any time, we also maintain a lock on the socket pair. Note one peculiarity regarding locking here:
+ * the UID lock on disk is protected via a BSD file lock (i.e. an fd-bound lock), so that the lock is kept in
+ * place as long as there's a reference to the fd open. The lock on the storage socket pair however is a POSIX
+ * file lock (i.e. a process-bound lock), as all users share the same fd of this (after all it is anonymous,
+ * nobody else could get any access to it except via our own fd) and we want to synchronize access between all
+ * processes that have access to it. */
+
+ d = hashmap_get(m->dynamic_users, name);
+ if (d) {
+ /* We already have a structure for the dynamic user, let's increase the ref count and reuse it */
+ d->n_ref++;
+ *ret = d;
+ return 0;
+ }
+
+ if (!valid_user_group_name_or_id(name))
+ return -EINVAL;
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, storage_socket) < 0)
+ return -errno;
+
+ r = dynamic_user_add(m, name, storage_socket, &d);
+ if (r < 0)
+ return r;
+
+ storage_socket[0] = storage_socket[1] = -1;
+
+ if (ret) {
+ d->n_ref++;
+ *ret = d;
+ }
+
+ return 1;
+}
+
+static int pick_uid(const char *name, uid_t *ret_uid) {
+
+ static const uint8_t hash_key[] = {
+ 0x37, 0x53, 0x7e, 0x31, 0xcf, 0xce, 0x48, 0xf5,
+ 0x8a, 0xbb, 0x39, 0x57, 0x8d, 0xd9, 0xec, 0x59
+ };
+
+ unsigned n_tries = 100;
+ uid_t candidate;
+ int r;
+
+ /* A static user by this name does not exist yet. Let's find a free ID then, and use that. We start with a UID
+ * generated as hash from the user name. */
+ candidate = UID_CLAMP_INTO_RANGE(siphash24(name, strlen(name), hash_key));
+
+ (void) mkdir("/run/systemd/dynamic-uid", 0755);
+
+ for (;;) {
+ char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1];
+ _cleanup_close_ int lock_fd = -1;
+ ssize_t l;
+
+ if (--n_tries <= 0) /* Give up retrying eventually */
+ return -EBUSY;
+
+ if (candidate < UID_PICK_MIN || candidate > UID_PICK_MAX)
+ goto next;
+
+ xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, candidate);
+
+ for (;;) {
+ struct stat st;
+
+ lock_fd = open(lock_path, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
+ if (lock_fd < 0)
+ return -errno;
+
+ r = flock(lock_fd, LOCK_EX|LOCK_NB); /* Try to get a BSD file lock on the UID lock file */
+ if (r < 0) {
+ if (errno == EBUSY || errno == EAGAIN)
+ goto next; /* already in use */
+
+ return -errno;
+ }
+
+ if (fstat(lock_fd, &st) < 0)
+ return -errno;
+ if (st.st_nlink > 0)
+ break;
+
+ /* Oh, bummer, we got got the lock, but the file was unlinked between the time we opened it and
+ * got the lock. Close it, and try again. */
+ lock_fd = safe_close(lock_fd);
+ }
+
+ /* Some superficial check whether this UID/GID might already be taken by some static user */
+ if (getpwuid(candidate) || getgrgid((gid_t) candidate)) {
+ (void) unlink(lock_path);
+ goto next;
+ }
+
+ /* Let's store the user name in the lock file, so that we can use it for looking up the username for a UID */
+ l = pwritev(lock_fd,
+ (struct iovec[2]) {
+ { .iov_base = (char*) name, .iov_len = strlen(name) },
+ { .iov_base = (char[1]) { '\n' }, .iov_len = 1 }
+ }, 2, 0);
+ if (l < 0) {
+ (void) unlink(lock_path);
+ return -errno;
+ }
+
+ (void) ftruncate(lock_fd, l);
+
+ *ret_uid = candidate;
+ r = lock_fd;
+ lock_fd = -1;
+
+ return r;
+
+ next:
+ /* Pick another random UID, and see if that works for us. */
+ random_bytes(&candidate, sizeof(candidate));
+ candidate = UID_CLAMP_INTO_RANGE(candidate);
+ }
+}
+
+static int dynamic_user_pop(DynamicUser *d, uid_t *ret_uid, int *ret_lock_fd) {
+ uid_t uid = UID_INVALID;
+ struct iovec iov = {
+ .iov_base = &uid,
+ .iov_len = sizeof(uid),
+ };
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(int))];
+ } control = {};
+ struct msghdr mh = {
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ struct cmsghdr *cmsg;
+
+ ssize_t k;
+ int lock_fd = -1;
+
+ assert(d);
+ assert(ret_uid);
+ assert(ret_lock_fd);
+
+ /* Read the UID and lock fd that is stored in the storage AF_UNIX socket. This should be called with the lock
+ * on the socket taken. */
+
+ k = recvmsg(d->storage_socket[0], &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC);
+ if (k < 0)
+ return -errno;
+
+ cmsg = cmsg_find(&mh, SOL_SOCKET, SCM_RIGHTS, CMSG_LEN(sizeof(int)));
+ if (cmsg)
+ lock_fd = *(int*) CMSG_DATA(cmsg);
+ else
+ cmsg_close_all(&mh); /* just in case... */
+
+ *ret_uid = uid;
+ *ret_lock_fd = lock_fd;
+
+ return 0;
+}
+
+static int dynamic_user_push(DynamicUser *d, uid_t uid, int lock_fd) {
+ struct iovec iov = {
+ .iov_base = &uid,
+ .iov_len = sizeof(uid),
+ };
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(int))];
+ } control = {};
+ struct msghdr mh = {
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ ssize_t k;
+
+ assert(d);
+
+ /* Store the UID and lock_fd in the storage socket. This should be called with the socket pair lock taken. */
+
+ if (lock_fd >= 0) {
+ struct cmsghdr *cmsg;
+
+ cmsg = CMSG_FIRSTHDR(&mh);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cmsg), &lock_fd, sizeof(int));
+
+ mh.msg_controllen = CMSG_SPACE(sizeof(int));
+ } else {
+ mh.msg_control = NULL;
+ mh.msg_controllen = 0;
+ }
+
+ k = sendmsg(d->storage_socket[1], &mh, MSG_DONTWAIT|MSG_NOSIGNAL);
+ if (k < 0)
+ return -errno;
+
+ return 0;
+}
+
+static void unlink_uid_lock(int lock_fd, uid_t uid) {
+ char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1];
+
+ if (lock_fd < 0)
+ return;
+
+ xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, uid);
+ (void) unlink_noerrno(lock_path);
+}
+
+int dynamic_user_realize(DynamicUser *d, uid_t *ret) {
+
+ _cleanup_close_ int etc_passwd_lock_fd = -1, uid_lock_fd = -1;
+ uid_t uid = UID_INVALID;
+ int r;
+
+ assert(d);
+
+ /* Acquire a UID for the user name. This will allocate a UID for the user name if the user doesn't exist
+ * yet. If it already exists its existing UID/GID will be reused. */
+
+ if (lockf(d->storage_socket[0], F_LOCK, 0) < 0)
+ return -errno;
+
+ r = dynamic_user_pop(d, &uid, &uid_lock_fd);
+ if (r < 0) {
+ int new_uid_lock_fd;
+ uid_t new_uid;
+
+ if (r != -EAGAIN)
+ goto finish;
+
+ /* OK, nothing stored yet, let's try to find something useful. While we are working on this release the
+ * lock however, so that nobody else blocks on our NSS lookups. */
+ (void) lockf(d->storage_socket[0], F_ULOCK, 0);
+
+ /* Let's see if a proper, static user or group by this name exists. Try to take the lock on
+ * /etc/passwd, if that fails with EROFS then /etc is read-only. In that case it's fine if we don't
+ * take the lock, given that users can't be added there anyway in this case. */
+ etc_passwd_lock_fd = take_etc_passwd_lock(NULL);
+ if (etc_passwd_lock_fd < 0 && etc_passwd_lock_fd != -EROFS)
+ return etc_passwd_lock_fd;
+
+ /* First, let's parse this as numeric UID */
+ r = parse_uid(d->name, &uid);
+ if (r < 0) {
+ struct passwd *p;
+ struct group *g;
+
+ /* OK, this is not a numeric UID. Let's see if there's a user by this name */
+ p = getpwnam(d->name);
+ if (p)
+ uid = p->pw_uid;
+
+ /* Let's see if there's a group by this name */
+ g = getgrnam(d->name);
+ if (g) {
+ /* If the UID/GID of the user/group of the same don't match, refuse operation */
+ if (uid != UID_INVALID && uid != (uid_t) g->gr_gid)
+ return -EILSEQ;
+
+ uid = (uid_t) g->gr_gid;
+ }
+ }
+
+ if (uid == UID_INVALID) {
+ /* No static UID assigned yet, excellent. Let's pick a new dynamic one, and lock it. */
+
+ uid_lock_fd = pick_uid(d->name, &uid);
+ if (uid_lock_fd < 0)
+ return uid_lock_fd;
+ }
+
+ /* So, we found a working UID/lock combination. Let's see if we actually still need it. */
+ if (lockf(d->storage_socket[0], F_LOCK, 0) < 0) {
+ unlink_uid_lock(uid_lock_fd, uid);
+ return -errno;
+ }
+
+ r = dynamic_user_pop(d, &new_uid, &new_uid_lock_fd);
+ if (r < 0) {
+ if (r != -EAGAIN) {
+ /* OK, something bad happened, let's get rid of the bits we acquired. */
+ unlink_uid_lock(uid_lock_fd, uid);
+ goto finish;
+ }
+
+ /* Great! Nothing is stored here, still. Store our newly acquired data. */
+ } else {
+ /* Hmm, so as it appears there's now something stored in the storage socket. Throw away what we
+ * acquired, and use what's stored now. */
+
+ unlink_uid_lock(uid_lock_fd, uid);
+ safe_close(uid_lock_fd);
+
+ uid = new_uid;
+ uid_lock_fd = new_uid_lock_fd;
+ }
+ }
+
+ /* If the UID/GID was already allocated dynamically, push the data we popped out back in. If it was already
+ * allocated statically, push the UID back too, but do not push the lock fd in. If we allocated the UID
+ * dynamically right here, push that in along with the lock fd for it. */
+ r = dynamic_user_push(d, uid, uid_lock_fd);
+ if (r < 0)
+ goto finish;
+
+ *ret = uid;
+ r = 0;
+
+finish:
+ (void) lockf(d->storage_socket[0], F_ULOCK, 0);
+ return r;
+}
+
+int dynamic_user_current(DynamicUser *d, uid_t *ret) {
+ _cleanup_close_ int lock_fd = -1;
+ uid_t uid;
+ int r;
+
+ assert(d);
+ assert(ret);
+
+ /* Get the currently assigned UID for the user, if there's any. This simply pops the data from the storage socket, and pushes it back in right-away. */
+
+ if (lockf(d->storage_socket[0], F_LOCK, 0) < 0)
+ return -errno;
+
+ r = dynamic_user_pop(d, &uid, &lock_fd);
+ if (r < 0)
+ goto finish;
+
+ r = dynamic_user_push(d, uid, lock_fd);
+ if (r < 0)
+ goto finish;
+
+ *ret = uid;
+ r = 0;
+
+finish:
+ (void) lockf(d->storage_socket[0], F_ULOCK, 0);
+ return r;
+}
+
+DynamicUser* dynamic_user_ref(DynamicUser *d) {
+ if (!d)
+ return NULL;
+
+ assert(d->n_ref > 0);
+ d->n_ref++;
+
+ return d;
+}
+
+DynamicUser* dynamic_user_unref(DynamicUser *d) {
+ if (!d)
+ return NULL;
+
+ /* Note that this doesn't actually release any resources itself. If a dynamic user should be fully destroyed
+ * and its UID released, use dynamic_user_destroy() instead. NB: the dynamic user table may contain entries
+ * with no references, which is commonly the case right before a daemon reload. */
+
+ assert(d->n_ref > 0);
+ d->n_ref--;
+
+ return NULL;
+}
+
+static int dynamic_user_close(DynamicUser *d) {
+ _cleanup_close_ int lock_fd = -1;
+ uid_t uid;
+ int r;
+
+ /* Release the user ID, by releasing the lock on it, and emptying the storage socket. After this the user is
+ * unrealized again, much like it was after it the DynamicUser object was first allocated. */
+
+ if (lockf(d->storage_socket[0], F_LOCK, 0) < 0)
+ return -errno;
+
+ r = dynamic_user_pop(d, &uid, &lock_fd);
+ if (r == -EAGAIN) {
+ /* User wasn't realized yet, nothing to do. */
+ r = 0;
+ goto finish;
+ }
+ if (r < 0)
+ goto finish;
+
+ /* This dynamic user was realized and dynamically allocated. In this case, let's remove the lock file. */
+ unlink_uid_lock(lock_fd, uid);
+ r = 1;
+
+finish:
+ (void) lockf(d->storage_socket[0], F_ULOCK, 0);
+ return r;
+}
+
+DynamicUser* dynamic_user_destroy(DynamicUser *d) {
+ if (!d)
+ return NULL;
+
+ /* Drop a reference to a DynamicUser object, and destroy the user completely if this was the last
+ * reference. This is called whenever a service is shut down and wants its dynamic UID gone. Note that
+ * dynamic_user_unref() is what is called whenever a service is simply freed, for example during a reload
+ * cycle, where the dynamic users should not be destroyed, but our datastructures should. */
+
+ dynamic_user_unref(d);
+
+ if (d->n_ref > 0)
+ return NULL;
+
+ (void) dynamic_user_close(d);
+ return dynamic_user_free(d);
+}
+
+int dynamic_user_serialize(Manager *m, FILE *f, FDSet *fds) {
+ DynamicUser *d;
+ Iterator i;
+
+ assert(m);
+ assert(f);
+ assert(fds);
+
+ /* Dump the dynamic user database into the manager serialization, to deal with daemon reloads. */
+
+ HASHMAP_FOREACH(d, m->dynamic_users, i) {
+ int copy0, copy1;
+
+ copy0 = fdset_put_dup(fds, d->storage_socket[0]);
+ if (copy0 < 0)
+ return copy0;
+
+ copy1 = fdset_put_dup(fds, d->storage_socket[1]);
+ if (copy1 < 0)
+ return copy1;
+
+ fprintf(f, "dynamic-user=%s %i %i\n", d->name, copy0, copy1);
+ }
+
+ return 0;
+}
+
+void dynamic_user_deserialize_one(Manager *m, const char *value, FDSet *fds) {
+ _cleanup_free_ char *name = NULL, *s0 = NULL, *s1 = NULL;
+ int r, fd0, fd1;
+
+ assert(m);
+ assert(value);
+ assert(fds);
+
+ /* Parse the serialization again, after a daemon reload */
+
+ r = extract_many_words(&value, NULL, 0, &name, &s0, &s1, NULL);
+ if (r != 3 || !isempty(value)) {
+ log_debug("Unable to parse dynamic user line.");
+ return;
+ }
+
+ if (safe_atoi(s0, &fd0) < 0 || !fdset_contains(fds, fd0)) {
+ log_debug("Unable to process dynamic user fd specification.");
+ return;
+ }
+
+ if (safe_atoi(s1, &fd1) < 0 || !fdset_contains(fds, fd1)) {
+ log_debug("Unable to process dynamic user fd specification.");
+ return;
+ }
+
+ r = dynamic_user_add(m, name, (int[]) { fd0, fd1 }, NULL);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to add dynamic user: %m");
+ return;
+ }
+
+ (void) fdset_remove(fds, fd0);
+ (void) fdset_remove(fds, fd1);
+}
+
+void dynamic_user_vacuum(Manager *m, bool close_user) {
+ DynamicUser *d;
+ Iterator i;
+
+ assert(m);
+
+ /* Empty the dynamic user database, optionally cleaning up orphaned dynamic users, i.e. destroy and free users
+ * to which no reference exist. This is called after a daemon reload finished, in order to destroy users which
+ * might not be referenced anymore. */
+
+ HASHMAP_FOREACH(d, m->dynamic_users, i) {
+ if (d->n_ref > 0)
+ continue;
+
+ if (close_user) {
+ log_debug("Removing orphaned dynamic user %s", d->name);
+ (void) dynamic_user_close(d);
+ }
+
+ dynamic_user_free(d);
+ }
+}
+
+int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret) {
+ char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1];
+ _cleanup_free_ char *user = NULL;
+ uid_t check_uid;
+ int r;
+
+ assert(m);
+ assert(ret);
+
+ /* A friendly way to translate a dynamic user's UID into a his name. */
+
+ if (uid < UID_PICK_MIN)
+ return -ESRCH;
+ if (uid > UID_PICK_MAX)
+ return -ESRCH;
+
+ xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, uid);
+ r = read_one_line_file(lock_path, &user);
+ if (r == -ENOENT)
+ return -ESRCH;
+ if (r < 0)
+ return r;
+
+ /* The lock file might be stale, hence let's verify the data before we return it */
+ r = dynamic_user_lookup_name(m, user, &check_uid);
+ if (r < 0)
+ return r;
+ if (check_uid != uid) /* lock file doesn't match our own idea */
+ return -ESRCH;
+
+ *ret = user;
+ user = NULL;
+
+ return 0;
+}
+
+int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret) {
+ DynamicUser *d;
+ int r;
+
+ assert(m);
+ assert(name);
+ assert(ret);
+
+ /* A friendly call for translating a dynamic user's name into its UID */
+
+ d = hashmap_get(m->dynamic_users, name);
+ if (!d)
+ return -ESRCH;
+
+ r = dynamic_user_current(d, ret);
+ if (r == -EAGAIN) /* not realized yet? */
+ return -ESRCH;
+
+ return r;
+}
+
+int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group) {
+ bool acquired = false;
+ int r;
+
+ assert(creds);
+ assert(m);
+
+ /* A DynamicUser object encapsulates an allocation of both a UID and a GID for a specific name. However, some
+ * services use different user and groups. For cases like that there's DynamicCreds containing a pair of user
+ * and group. This call allocates a pair. */
+
+ if (!creds->user && user) {
+ r = dynamic_user_acquire(m, user, &creds->user);
+ if (r < 0)
+ return r;
+
+ acquired = true;
+ }
+
+ if (!creds->group) {
+
+ if (creds->user && (!group || streq_ptr(user, group)))
+ creds->group = dynamic_user_ref(creds->user);
+ else {
+ r = dynamic_user_acquire(m, group, &creds->group);
+ if (r < 0) {
+ if (acquired)
+ creds->user = dynamic_user_unref(creds->user);
+ return r;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int dynamic_creds_realize(DynamicCreds *creds, uid_t *uid, gid_t *gid) {
+ uid_t u = UID_INVALID;
+ gid_t g = GID_INVALID;
+ int r;
+
+ assert(creds);
+ assert(uid);
+ assert(gid);
+
+ /* Realize both the referenced user and group */
+
+ if (creds->user) {
+ r = dynamic_user_realize(creds->user, &u);
+ if (r < 0)
+ return r;
+ }
+
+ if (creds->group && creds->group != creds->user) {
+ r = dynamic_user_realize(creds->group, &g);
+ if (r < 0)
+ return r;
+ } else
+ g = u;
+
+ *uid = u;
+ *gid = g;
+
+ return 0;
+}
+
+void dynamic_creds_unref(DynamicCreds *creds) {
+ assert(creds);
+
+ creds->user = dynamic_user_unref(creds->user);
+ creds->group = dynamic_user_unref(creds->group);
+}
+
+void dynamic_creds_destroy(DynamicCreds *creds) {
+ assert(creds);
+
+ creds->user = dynamic_user_destroy(creds->user);
+ creds->group = dynamic_user_destroy(creds->group);
+}
diff --git a/src/core/dynamic-user.h b/src/core/dynamic-user.h
new file mode 100644
index 0000000000..0b8bce1a72
--- /dev/null
+++ b/src/core/dynamic-user.h
@@ -0,0 +1,66 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct DynamicUser DynamicUser;
+
+typedef struct DynamicCreds {
+ /* A combination of a dynamic user and group */
+ DynamicUser *user;
+ DynamicUser *group;
+} DynamicCreds;
+
+#include "manager.h"
+
+/* Note that this object always allocates a pair of user and group under the same name, even if one of them isn't
+ * used. This means, if you want to allocate a group and user pair, and they might have two different names, then you
+ * need to allocated two of these objects. DynamicCreds below makes that easy. */
+struct DynamicUser {
+ int n_ref;
+ Manager *manager;
+
+ /* An AF_UNIX socket pair that contains a datagram containing both the numeric ID assigned, as well as a lock
+ * file fd locking the user ID we picked. */
+ int storage_socket[2];
+
+ char name[];
+};
+
+int dynamic_user_acquire(Manager *m, const char *name, DynamicUser **ret);
+
+int dynamic_user_realize(DynamicUser *d, uid_t *ret);
+int dynamic_user_current(DynamicUser *d, uid_t *ret);
+
+DynamicUser* dynamic_user_ref(DynamicUser *d);
+DynamicUser* dynamic_user_unref(DynamicUser *d);
+DynamicUser* dynamic_user_destroy(DynamicUser *d);
+
+int dynamic_user_serialize(Manager *m, FILE *f, FDSet *fds);
+void dynamic_user_deserialize_one(Manager *m, const char *value, FDSet *fds);
+void dynamic_user_vacuum(Manager *m, bool close_user);
+
+int dynamic_user_lookup_uid(Manager *m, uid_t uid, char **ret);
+int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret);
+
+int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, const char *group);
+int dynamic_creds_realize(DynamicCreds *creds, uid_t *uid, gid_t *gid);
+
+void dynamic_creds_unref(DynamicCreds *creds);
+void dynamic_creds_destroy(DynamicCreds *creds);
diff --git a/src/core/execute.c b/src/core/execute.c
index 2cef70e668..77a75245cb 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -219,12 +219,36 @@ static void exec_context_tty_reset(const ExecContext *context, const ExecParamet
(void) vt_disallocate(path);
}
+static bool is_terminal_input(ExecInput i) {
+ return IN_SET(i,
+ EXEC_INPUT_TTY,
+ EXEC_INPUT_TTY_FORCE,
+ EXEC_INPUT_TTY_FAIL);
+}
+
static bool is_terminal_output(ExecOutput o) {
- return
- o == EXEC_OUTPUT_TTY ||
- o == EXEC_OUTPUT_SYSLOG_AND_CONSOLE ||
- o == EXEC_OUTPUT_KMSG_AND_CONSOLE ||
- o == EXEC_OUTPUT_JOURNAL_AND_CONSOLE;
+ return IN_SET(o,
+ EXEC_OUTPUT_TTY,
+ EXEC_OUTPUT_SYSLOG_AND_CONSOLE,
+ EXEC_OUTPUT_KMSG_AND_CONSOLE,
+ EXEC_OUTPUT_JOURNAL_AND_CONSOLE);
+}
+
+static bool exec_context_needs_term(const ExecContext *c) {
+ assert(c);
+
+ /* Return true if the execution context suggests we should set $TERM to something useful. */
+
+ if (is_terminal_input(c->std_input))
+ return true;
+
+ if (is_terminal_output(c->std_output))
+ return true;
+
+ if (is_terminal_output(c->std_error))
+ return true;
+
+ return !!c->tty_path;
}
static int open_null_as(int flags, int nfd) {
@@ -289,7 +313,15 @@ static int connect_journal_socket(int fd, uid_t uid, gid_t gid) {
return r;
}
-static int connect_logger_as(const ExecContext *context, ExecOutput output, const char *ident, const char *unit_id, int nfd, uid_t uid, gid_t gid) {
+static int connect_logger_as(
+ Unit *unit,
+ const ExecContext *context,
+ ExecOutput output,
+ const char *ident,
+ int nfd,
+ uid_t uid,
+ gid_t gid) {
+
int fd, r;
assert(context);
@@ -310,7 +342,7 @@ static int connect_logger_as(const ExecContext *context, ExecOutput output, cons
return -errno;
}
- fd_inc_sndbuf(fd, SNDBUF_SIZE);
+ (void) fd_inc_sndbuf(fd, SNDBUF_SIZE);
dprintf(fd,
"%s\n"
@@ -321,18 +353,18 @@ static int connect_logger_as(const ExecContext *context, ExecOutput output, cons
"%i\n"
"%i\n",
context->syslog_identifier ? context->syslog_identifier : ident,
- unit_id,
+ unit->id,
context->syslog_priority,
!!context->syslog_level_prefix,
output == EXEC_OUTPUT_SYSLOG || output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE,
output == EXEC_OUTPUT_KMSG || output == EXEC_OUTPUT_KMSG_AND_CONSOLE,
is_terminal_output(output));
- if (fd != nfd) {
- r = dup2(fd, nfd) < 0 ? -errno : nfd;
- safe_close(fd);
- } else
- r = nfd;
+ if (fd == nfd)
+ return nfd;
+
+ r = dup2(fd, nfd) < 0 ? -errno : nfd;
+ safe_close(fd);
return r;
}
@@ -355,13 +387,6 @@ static int open_terminal_as(const char *path, mode_t mode, int nfd) {
return r;
}
-static bool is_terminal_input(ExecInput i) {
- return
- i == EXEC_INPUT_TTY ||
- i == EXEC_INPUT_TTY_FORCE ||
- i == EXEC_INPUT_TTY_FAIL;
-}
-
static int fixup_input(ExecInput std_input, int socket_fd, bool apply_tty_stdin) {
if (is_terminal_input(std_input) && !apply_tty_stdin)
@@ -446,7 +471,10 @@ static int setup_output(
int fileno,
int socket_fd,
const char *ident,
- uid_t uid, gid_t gid) {
+ uid_t uid,
+ gid_t gid,
+ dev_t *journal_stream_dev,
+ ino_t *journal_stream_ino) {
ExecOutput o;
ExecInput i;
@@ -456,6 +484,8 @@ static int setup_output(
assert(context);
assert(params);
assert(ident);
+ assert(journal_stream_dev);
+ assert(journal_stream_ino);
if (fileno == STDOUT_FILENO && params->stdout_fd >= 0) {
@@ -531,10 +561,21 @@ static int setup_output(
case EXEC_OUTPUT_KMSG_AND_CONSOLE:
case EXEC_OUTPUT_JOURNAL:
case EXEC_OUTPUT_JOURNAL_AND_CONSOLE:
- r = connect_logger_as(context, o, ident, unit->id, fileno, uid, gid);
+ r = connect_logger_as(unit, context, o, ident, fileno, uid, gid);
if (r < 0) {
log_unit_error_errno(unit, r, "Failed to connect %s to the journal socket, ignoring: %m", fileno == STDOUT_FILENO ? "stdout" : "stderr");
r = open_null_as(O_WRONLY, fileno);
+ } else {
+ struct stat st;
+
+ /* If we connected this fd to the journal via a stream, patch the device/inode into the passed
+ * parameters, but only then. This is useful so that we can set $JOURNAL_STREAM that permits
+ * services to detect whether they are connected to the journal or not. */
+
+ if (fstat(fileno, &st) >= 0) {
+ *journal_stream_dev = st.st_dev;
+ *journal_stream_ino = st.st_ino;
+ }
}
return r;
@@ -552,6 +593,10 @@ static int chown_terminal(int fd, uid_t uid) {
assert(fd >= 0);
+ /* Before we chown/chmod the TTY, let's ensure this is actually a tty */
+ if (isatty(fd) < 1)
+ return 0;
+
/* This might fail. What matters are the results. */
(void) fchown(fd, uid, -1);
(void) fchmod(fd, TTY_MODE);
@@ -795,7 +840,7 @@ static int setup_pam(
const char *user,
uid_t uid,
const char *tty,
- char ***pam_env,
+ char ***env,
int fds[], unsigned n_fds) {
static const struct pam_conv conv = {
@@ -807,14 +852,14 @@ static int setup_pam(
pam_handle_t *handle = NULL;
sigset_t old_ss;
int pam_code = PAM_SUCCESS, r;
- char **e = NULL;
+ char **nv, **e = NULL;
bool close_session = false;
pid_t pam_pid = 0, parent_pid;
int flags = 0;
assert(name);
assert(user);
- assert(pam_env);
+ assert(env);
/* We set up PAM in the parent process, then fork. The child
* will then stay around until killed via PR_GET_PDEATHSIG or
@@ -842,6 +887,12 @@ static int setup_pam(
goto fail;
}
+ STRV_FOREACH(nv, *env) {
+ pam_code = pam_putenv(handle, *nv);
+ if (pam_code != PAM_SUCCESS)
+ goto fail;
+ }
+
pam_code = pam_acct_mgmt(handle, flags);
if (pam_code != PAM_SUCCESS)
goto fail;
@@ -962,8 +1013,8 @@ static int setup_pam(
if (!barrier_place_and_sync(&barrier))
log_error("PAM initialization failed");
- *pam_env = e;
- e = NULL;
+ strv_free(*env);
+ *env = e;
return 0;
@@ -1203,7 +1254,7 @@ static int apply_memory_deny_write_execute(const ExecContext *c) {
r = seccomp_rule_add(
seccomp,
- SCMP_ACT_KILL,
+ SCMP_ACT_ERRNO(EPERM),
SCMP_SYS(mmap),
1,
SCMP_A2(SCMP_CMP_MASKED_EQ, PROT_EXEC|PROT_WRITE, PROT_EXEC|PROT_WRITE));
@@ -1212,7 +1263,7 @@ static int apply_memory_deny_write_execute(const ExecContext *c) {
r = seccomp_rule_add(
seccomp,
- SCMP_ACT_KILL,
+ SCMP_ACT_ERRNO(EPERM),
SCMP_SYS(mprotect),
1,
SCMP_A2(SCMP_CMP_MASKED_EQ, PROT_EXEC, PROT_EXEC));
@@ -1230,6 +1281,76 @@ finish:
return r;
}
+static int apply_restrict_realtime(const ExecContext *c) {
+ static const int permitted_policies[] = {
+ SCHED_OTHER,
+ SCHED_BATCH,
+ SCHED_IDLE,
+ };
+
+ scmp_filter_ctx *seccomp;
+ unsigned i;
+ int r, p, max_policy = 0;
+
+ assert(c);
+
+ seccomp = seccomp_init(SCMP_ACT_ALLOW);
+ if (!seccomp)
+ return -ENOMEM;
+
+ /* Determine the highest policy constant we want to allow */
+ for (i = 0; i < ELEMENTSOF(permitted_policies); i++)
+ if (permitted_policies[i] > max_policy)
+ max_policy = permitted_policies[i];
+
+ /* Go through all policies with lower values than that, and block them -- unless they appear in the
+ * whitelist. */
+ for (p = 0; p < max_policy; p++) {
+ bool good = false;
+
+ /* Check if this is in the whitelist. */
+ for (i = 0; i < ELEMENTSOF(permitted_policies); i++)
+ if (permitted_policies[i] == p) {
+ good = true;
+ break;
+ }
+
+ if (good)
+ continue;
+
+ /* Deny this policy */
+ r = seccomp_rule_add(
+ seccomp,
+ SCMP_ACT_ERRNO(EPERM),
+ SCMP_SYS(sched_setscheduler),
+ 1,
+ SCMP_A1(SCMP_CMP_EQ, p));
+ if (r < 0)
+ goto finish;
+ }
+
+ /* Blacklist all other policies, i.e. the ones with higher values. Note that all comparisons are unsigned here,
+ * hence no need no check for < 0 values. */
+ r = seccomp_rule_add(
+ seccomp,
+ SCMP_ACT_ERRNO(EPERM),
+ SCMP_SYS(sched_setscheduler),
+ 1,
+ SCMP_A1(SCMP_CMP_GT, max_policy));
+ if (r < 0)
+ goto finish;
+
+ r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0);
+ if (r < 0)
+ goto finish;
+
+ r = seccomp_load(seccomp);
+
+finish:
+ seccomp_release(seccomp);
+ return r;
+}
+
#endif
static void do_idle_pipe_dance(int idle_pipe[4]) {
@@ -1268,6 +1389,8 @@ static int build_environment(
const char *home,
const char *username,
const char *shell,
+ dev_t journal_stream_dev,
+ ino_t journal_stream_ino,
char ***ret) {
_cleanup_strv_free_ char **our_env = NULL;
@@ -1277,7 +1400,7 @@ static int build_environment(
assert(c);
assert(ret);
- our_env = new0(char*, 11);
+ our_env = new0(char*, 12);
if (!our_env)
return -ENOMEM;
@@ -1338,19 +1461,35 @@ static int build_environment(
our_env[n_env++] = x;
}
- if (is_terminal_input(c->std_input) ||
- c->std_output == EXEC_OUTPUT_TTY ||
- c->std_error == EXEC_OUTPUT_TTY ||
- c->tty_path) {
+ if (exec_context_needs_term(c)) {
+ const char *tty_path, *term = NULL;
+
+ tty_path = exec_context_tty_path(c);
- x = strdup(default_term_for_tty(exec_context_tty_path(c)));
+ /* If we are forked off PID 1 and we are supposed to operate on /dev/console, then let's try to inherit
+ * the $TERM set for PID 1. This is useful for containers so that the $TERM the container manager
+ * passes to PID 1 ends up all the way in the console login shown. */
+
+ if (path_equal(tty_path, "/dev/console") && getppid() == 1)
+ term = getenv("TERM");
+ if (!term)
+ term = default_term_for_tty(tty_path);
+
+ x = strappend("TERM=", term);
if (!x)
return -ENOMEM;
our_env[n_env++] = x;
}
+ if (journal_stream_dev != 0 && journal_stream_ino != 0) {
+ if (asprintf(&x, "JOURNAL_STREAM=" DEV_FMT ":" INO_FMT, journal_stream_dev, journal_stream_ino) < 0)
+ return -ENOMEM;
+
+ our_env[n_env++] = x;
+ }
+
our_env[n_env++] = NULL;
- assert(n_env <= 11);
+ assert(n_env <= 12);
*ret = our_env;
our_env = NULL;
@@ -1394,9 +1533,9 @@ static bool exec_needs_mount_namespace(
assert(context);
assert(params);
- if (!strv_isempty(context->read_write_dirs) ||
- !strv_isempty(context->read_only_dirs) ||
- !strv_isempty(context->inaccessible_dirs))
+ if (!strv_isempty(context->read_write_paths) ||
+ !strv_isempty(context->read_only_paths) ||
+ !strv_isempty(context->inaccessible_paths))
return true;
if (context->mount_flags != 0)
@@ -1413,14 +1552,28 @@ static bool exec_needs_mount_namespace(
return false;
}
+static void append_socket_pair(int *array, unsigned *n, int pair[2]) {
+ assert(array);
+ assert(n);
+
+ if (!pair)
+ return;
+
+ if (pair[0] >= 0)
+ array[(*n)++] = pair[0];
+ if (pair[1] >= 0)
+ array[(*n)++] = pair[1];
+}
+
static int close_remaining_fds(
const ExecParameters *params,
ExecRuntime *runtime,
+ DynamicCreds *dcreds,
int socket_fd,
int *fds, unsigned n_fds) {
unsigned n_dont_close = 0;
- int dont_close[n_fds + 7];
+ int dont_close[n_fds + 11];
assert(params);
@@ -1438,11 +1591,14 @@ static int close_remaining_fds(
n_dont_close += n_fds;
}
- if (runtime) {
- if (runtime->netns_storage_socket[0] >= 0)
- dont_close[n_dont_close++] = runtime->netns_storage_socket[0];
- if (runtime->netns_storage_socket[1] >= 0)
- dont_close[n_dont_close++] = runtime->netns_storage_socket[1];
+ if (runtime)
+ append_socket_pair(dont_close, &n_dont_close, runtime->netns_storage_socket);
+
+ if (dcreds) {
+ if (dcreds->user)
+ append_socket_pair(dont_close, &n_dont_close, dcreds->user->storage_socket);
+ if (dcreds->group)
+ append_socket_pair(dont_close, &n_dont_close, dcreds->group->storage_socket);
}
return close_all_fds(dont_close, n_dont_close);
@@ -1454,19 +1610,22 @@ static int exec_child(
const ExecContext *context,
const ExecParameters *params,
ExecRuntime *runtime,
+ DynamicCreds *dcreds,
char **argv,
int socket_fd,
int *fds, unsigned n_fds,
char **files_env,
int *exit_status) {
- _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **pam_env = NULL, **final_env = NULL, **final_argv = NULL;
+ _cleanup_strv_free_ char **our_env = NULL, **pass_env = NULL, **accum_env = NULL, **final_argv = NULL;
_cleanup_free_ char *mac_selinux_context_net = NULL;
const char *username = NULL, *home = NULL, *shell = NULL, *wd;
+ dev_t journal_stream_dev = 0;
+ ino_t journal_stream_ino = 0;
+ bool needs_mount_namespace;
uid_t uid = UID_INVALID;
gid_t gid = GID_INVALID;
int i, r;
- bool needs_mount_namespace;
assert(unit);
assert(command);
@@ -1502,7 +1661,7 @@ static int exec_child(
log_forget_fds();
- r = close_remaining_fds(params, runtime, socket_fd, fds, n_fds);
+ r = close_remaining_fds(params, runtime, dcreds, socket_fd, fds, n_fds);
if (r < 0) {
*exit_status = EXIT_FDS;
return r;
@@ -1535,25 +1694,59 @@ static int exec_child(
}
}
- if (context->user) {
- username = context->user;
- r = get_user_creds(&username, &uid, &gid, &home, &shell);
+ if (context->dynamic_user && dcreds) {
+
+ /* Make sure we bypass our own NSS module for any NSS checks */
+ if (putenv((char*) "SYSTEMD_NSS_DYNAMIC_BYPASS=1") != 0) {
+ *exit_status = EXIT_USER;
+ return -errno;
+ }
+
+ r = dynamic_creds_realize(dcreds, &uid, &gid);
if (r < 0) {
*exit_status = EXIT_USER;
return r;
}
- }
- if (context->group) {
- const char *g = context->group;
+ if (uid == UID_INVALID || gid == GID_INVALID) {
+ *exit_status = EXIT_USER;
+ return -ESRCH;
+ }
- r = get_group_creds(&g, &gid);
- if (r < 0) {
- *exit_status = EXIT_GROUP;
- return r;
+ if (dcreds->user)
+ username = dcreds->user->name;
+
+ } else {
+ if (context->user) {
+ username = context->user;
+ r = get_user_creds(&username, &uid, &gid, &home, &shell);
+ if (r < 0) {
+ *exit_status = EXIT_USER;
+ return r;
+ }
+
+ /* Don't set $HOME or $SHELL if they are are not particularly enlightening anyway. */
+ if (isempty(home) || path_equal(home, "/"))
+ home = NULL;
+
+ if (isempty(shell) || PATH_IN_SET(shell,
+ "/bin/nologin",
+ "/sbin/nologin",
+ "/usr/bin/nologin",
+ "/usr/sbin/nologin"))
+ shell = NULL;
}
- }
+ if (context->group) {
+ const char *g = context->group;
+
+ r = get_group_creds(&g, &gid);
+ if (r < 0) {
+ *exit_status = EXIT_GROUP;
+ return r;
+ }
+ }
+ }
/* If a socket is connected to STDIN/STDOUT/STDERR, we
* must sure to drop O_NONBLOCK */
@@ -1566,13 +1759,13 @@ static int exec_child(
return r;
}
- r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, basename(command->path), uid, gid);
+ r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino);
if (r < 0) {
*exit_status = EXIT_STDOUT;
return r;
}
- r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, basename(command->path), uid, gid);
+ r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino);
if (r < 0) {
*exit_status = EXIT_STDERR;
return r;
@@ -1711,9 +1904,43 @@ static int exec_child(
}
}
+ r = build_environment(
+ context,
+ params,
+ n_fds,
+ home,
+ username,
+ shell,
+ journal_stream_dev,
+ journal_stream_ino,
+ &our_env);
+ if (r < 0) {
+ *exit_status = EXIT_MEMORY;
+ return r;
+ }
+
+ r = build_pass_environment(context, &pass_env);
+ if (r < 0) {
+ *exit_status = EXIT_MEMORY;
+ return r;
+ }
+
+ accum_env = strv_env_merge(5,
+ params->environment,
+ our_env,
+ pass_env,
+ context->environment,
+ files_env,
+ NULL);
+ if (!accum_env) {
+ *exit_status = EXIT_MEMORY;
+ return -ENOMEM;
+ }
+ accum_env = strv_env_clean(accum_env);
+
umask(context->umask);
- if (params->apply_permissions) {
+ if (params->apply_permissions && !command->privileged) {
r = enforce_groups(context, username, gid);
if (r < 0) {
*exit_status = EXIT_GROUP;
@@ -1747,7 +1974,7 @@ static int exec_child(
#endif
#ifdef HAVE_PAM
if (context->pam_name && username) {
- r = setup_pam(context->pam_name, username, uid, context->tty_path, &pam_env, fds, n_fds);
+ r = setup_pam(context->pam_name, username, uid, context->tty_path, &accum_env, fds, n_fds);
if (r < 0) {
*exit_status = EXIT_PAM;
return r;
@@ -1784,9 +2011,9 @@ static int exec_child(
r = setup_namespace(
params->apply_chroot ? context->root_directory : NULL,
- context->read_write_dirs,
- context->read_only_dirs,
- context->inaccessible_dirs,
+ context->read_write_paths,
+ context->read_only_paths,
+ context->inaccessible_paths,
tmp,
var,
context->private_devices,
@@ -1838,7 +2065,7 @@ static int exec_child(
}
#ifdef HAVE_SELINUX
- if (params->apply_permissions && mac_selinux_use() && params->selinux_context_net && socket_fd >= 0) {
+ if (params->apply_permissions && mac_selinux_use() && params->selinux_context_net && socket_fd >= 0 && !command->privileged) {
r = mac_selinux_get_child_mls_label(socket_fd, command->path, context->selinux_context, &mac_selinux_context_net);
if (r < 0) {
*exit_status = EXIT_SELINUX_CONTEXT;
@@ -1863,7 +2090,7 @@ static int exec_child(
return r;
}
- if (params->apply_permissions) {
+ if (params->apply_permissions && !command->privileged) {
bool use_address_families = context->address_families_whitelist ||
!set_isempty(context->address_families);
@@ -1873,10 +2100,20 @@ static int exec_child(
int secure_bits = context->secure_bits;
for (i = 0; i < _RLIMIT_MAX; i++) {
+
if (!context->rlimit[i])
continue;
- if (setrlimit_closest(i, context->rlimit[i]) < 0) {
+ r = setrlimit_closest(i, context->rlimit[i]);
+ if (r < 0) {
+ *exit_status = EXIT_LIMITS;
+ return r;
+ }
+ }
+
+ /* Set the RTPRIO resource limit to 0, but only if nothing else was explicitly requested. */
+ if (context->restrict_realtime && !context->rlimit[RLIMIT_RTPRIO]) {
+ if (setrlimit(RLIMIT_RTPRIO, &RLIMIT_MAKE_CONST(0)) < 0) {
*exit_status = EXIT_LIMITS;
return -errno;
}
@@ -1937,7 +2174,7 @@ static int exec_child(
}
if (context->no_new_privileges ||
- (!have_effective_cap(CAP_SYS_ADMIN) && (use_address_families || use_syscall_filter)))
+ (!have_effective_cap(CAP_SYS_ADMIN) && (use_address_families || context->memory_deny_write_execute || context->restrict_realtime || use_syscall_filter)))
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
*exit_status = EXIT_NO_NEW_PRIVILEGES;
return -errno;
@@ -1959,6 +2196,15 @@ static int exec_child(
return r;
}
}
+
+ if (context->restrict_realtime) {
+ r = apply_restrict_realtime(context);
+ if (r < 0) {
+ *exit_status = EXIT_SECCOMP;
+ return r;
+ }
+ }
+
if (use_syscall_filter) {
r = apply_seccomp(context);
if (r < 0) {
@@ -1993,39 +2239,12 @@ static int exec_child(
#endif
}
- r = build_environment(context, params, n_fds, home, username, shell, &our_env);
- if (r < 0) {
- *exit_status = EXIT_MEMORY;
- return r;
- }
-
- r = build_pass_environment(context, &pass_env);
- if (r < 0) {
- *exit_status = EXIT_MEMORY;
- return r;
- }
-
- final_env = strv_env_merge(6,
- params->environment,
- our_env,
- pass_env,
- context->environment,
- files_env,
- pam_env,
- NULL);
- if (!final_env) {
- *exit_status = EXIT_MEMORY;
- return -ENOMEM;
- }
-
- final_argv = replace_env_argv(argv, final_env);
+ final_argv = replace_env_argv(argv, accum_env);
if (!final_argv) {
*exit_status = EXIT_MEMORY;
return -ENOMEM;
}
- final_env = strv_env_clean(final_env);
-
if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) {
_cleanup_free_ char *line;
@@ -2041,7 +2260,7 @@ static int exec_child(
}
}
- execve(command->path, final_argv, final_env);
+ execve(command->path, final_argv, accum_env);
*exit_status = EXIT_EXEC;
return -errno;
}
@@ -2051,6 +2270,7 @@ int exec_spawn(Unit *unit,
const ExecContext *context,
const ExecParameters *params,
ExecRuntime *runtime,
+ DynamicCreds *dcreds,
pid_t *ret) {
_cleanup_strv_free_ char **files_env = NULL;
@@ -2109,6 +2329,7 @@ int exec_spawn(Unit *unit,
context,
params,
runtime,
+ dcreds,
argv,
socket_fd,
fds, n_fds,
@@ -2183,9 +2404,9 @@ void exec_context_done(ExecContext *c) {
c->pam_name = mfree(c->pam_name);
- c->read_only_dirs = strv_free(c->read_only_dirs);
- c->read_write_dirs = strv_free(c->read_write_dirs);
- c->inaccessible_dirs = strv_free(c->inaccessible_dirs);
+ c->read_only_paths = strv_free(c->read_only_paths);
+ c->read_write_paths = strv_free(c->read_write_paths);
+ c->inaccessible_paths = strv_free(c->inaccessible_paths);
if (c->cpuset)
CPU_FREE(c->cpuset);
@@ -2419,7 +2640,8 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
"%sProtectHome: %s\n"
"%sProtectSystem: %s\n"
"%sIgnoreSIGPIPE: %s\n"
- "%sMemoryDenyWriteExecute: %s\n",
+ "%sMemoryDenyWriteExecute: %s\n"
+ "%sRestrictRealtime: %s\n",
prefix, c->umask,
prefix, c->working_directory ? c->working_directory : "/",
prefix, c->root_directory ? c->root_directory : "/",
@@ -2430,7 +2652,8 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
prefix, protect_home_to_string(c->protect_home),
prefix, protect_system_to_string(c->protect_system),
prefix, yes_no(c->ignore_sigpipe),
- prefix, yes_no(c->memory_deny_write_execute));
+ prefix, yes_no(c->memory_deny_write_execute),
+ prefix, yes_no(c->restrict_realtime));
STRV_FOREACH(e, c->environment)
fprintf(f, "%sEnvironment: %s\n", prefix, *e);
@@ -2580,6 +2803,8 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
if (c->group)
fprintf(f, "%sGroup: %s\n", prefix, c->group);
+ fprintf(f, "%sDynamicUser: %s\n", prefix, yes_no(c->dynamic_user));
+
if (strv_length(c->supplementary_groups) > 0) {
fprintf(f, "%sSupplementaryGroups:", prefix);
strv_fprintf(f, c->supplementary_groups);
@@ -2589,21 +2814,21 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
if (c->pam_name)
fprintf(f, "%sPAMName: %s\n", prefix, c->pam_name);
- if (strv_length(c->read_write_dirs) > 0) {
- fprintf(f, "%sReadWriteDirs:", prefix);
- strv_fprintf(f, c->read_write_dirs);
+ if (strv_length(c->read_write_paths) > 0) {
+ fprintf(f, "%sReadWritePaths:", prefix);
+ strv_fprintf(f, c->read_write_paths);
fputs("\n", f);
}
- if (strv_length(c->read_only_dirs) > 0) {
- fprintf(f, "%sReadOnlyDirs:", prefix);
- strv_fprintf(f, c->read_only_dirs);
+ if (strv_length(c->read_only_paths) > 0) {
+ fprintf(f, "%sReadOnlyPaths:", prefix);
+ strv_fprintf(f, c->read_only_paths);
fputs("\n", f);
}
- if (strv_length(c->inaccessible_dirs) > 0) {
- fprintf(f, "%sInaccessibleDirs:", prefix);
- strv_fprintf(f, c->inaccessible_dirs);
+ if (strv_length(c->inaccessible_paths) > 0) {
+ fprintf(f, "%sInaccessiblePaths:", prefix);
+ strv_fprintf(f, c->inaccessible_paths);
fputs("\n", f);
}
@@ -2684,7 +2909,7 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
bool exec_context_maintains_privileges(ExecContext *c) {
assert(c);
- /* Returns true if the process forked off would run run under
+ /* Returns true if the process forked off would run under
* an unchanged UID or as root. */
if (!c->user)
@@ -2919,7 +3144,7 @@ int exec_runtime_make(ExecRuntime **rt, ExecContext *c, const char *id) {
return r;
if (c->private_network && (*rt)->netns_storage_socket[0] < 0) {
- if (socketpair(AF_UNIX, SOCK_DGRAM, 0, (*rt)->netns_storage_socket) < 0)
+ if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, (*rt)->netns_storage_socket) < 0)
return -errno;
}
diff --git a/src/core/execute.h b/src/core/execute.h
index 464869d226..48cc18fbb3 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -30,6 +30,7 @@ typedef struct ExecParameters ExecParameters;
#include <stdio.h>
#include <sys/capability.h>
+#include "cgroup-util.h"
#include "fdset.h"
#include "list.h"
#include "missing.h"
@@ -81,7 +82,8 @@ struct ExecCommand {
char **argv;
ExecStatus exec_status;
LIST_FIELDS(ExecCommand, command); /* useful for chaining commands */
- bool ignore;
+ bool ignore:1;
+ bool privileged:1;
};
struct ExecRuntime {
@@ -90,6 +92,8 @@ struct ExecRuntime {
char *tmp_dir;
char *var_tmp_dir;
+ /* An AF_UNIX socket pair, that contains a datagram containing a file descriptor referring to the network
+ * namespace. */
int netns_storage_socket[2];
};
@@ -129,7 +133,7 @@ struct ExecContext {
bool ignore_sigpipe;
- /* Since resolving these names might might involve socket
+ /* Since resolving these names might involve socket
* connections and we don't want to deadlock ourselves these
* names are resolved on execution only and in the child
* process. */
@@ -151,7 +155,7 @@ struct ExecContext {
bool smack_process_label_ignore;
char *smack_process_label;
- char **read_write_dirs, **read_only_dirs, **inaccessible_dirs;
+ char **read_write_paths, **read_only_paths, **inaccessible_paths;
unsigned long mount_flags;
uint64_t capability_bounding_set;
@@ -172,6 +176,8 @@ struct ExecContext {
bool no_new_privileges;
+ bool dynamic_user;
+
/* This is not exposed to the user but available
* internally. We need it to make sure that whenever we spawn
* /usr/bin/mount it is run in the same process group as us so
@@ -192,17 +198,16 @@ struct ExecContext {
char **runtime_directory;
mode_t runtime_directory_mode;
+ bool memory_deny_write_execute;
+ bool restrict_realtime;
+
bool oom_score_adjust_set:1;
bool nice_set:1;
bool ioprio_set:1;
bool cpu_sched_set:1;
bool no_new_privileges_set:1;
- bool memory_deny_write_execute;
};
-#include "cgroup-util.h"
-#include "cgroup.h"
-
struct ExecParameters {
char **argv;
char **environment;
@@ -233,11 +238,15 @@ struct ExecParameters {
int stderr_fd;
};
+#include "unit.h"
+#include "dynamic-user.h"
+
int exec_spawn(Unit *unit,
ExecCommand *command,
const ExecContext *context,
const ExecParameters *exec_params,
ExecRuntime *runtime,
+ DynamicCreds *dynamic_creds,
pid_t *ret);
void exec_command_done(ExecCommand *c);
diff --git a/src/core/killall.c b/src/core/killall.c
index 09378f7085..a8b814e868 100644
--- a/src/core/killall.c
+++ b/src/core/killall.c
@@ -23,6 +23,7 @@
#include <unistd.h>
#include "alloc-util.h"
+#include "def.h"
#include "fd-util.h"
#include "formats-util.h"
#include "killall.h"
@@ -33,8 +34,6 @@
#include "terminal-util.h"
#include "util.h"
-#define TIMEOUT_USEC (10 * USEC_PER_SEC)
-
static bool ignore_proc(pid_t pid, bool warn_rootfs) {
_cleanup_fclose_ FILE *f = NULL;
char c;
@@ -80,7 +79,7 @@ static bool ignore_proc(pid_t pid, bool warn_rootfs) {
get_process_comm(pid, &comm);
if (r)
- log_notice("Process " PID_FMT " (%s) has been been marked to be excluded from killing. It is "
+ log_notice("Process " PID_FMT " (%s) has been marked to be excluded from killing. It is "
"running from the root file system, and thus likely to block re-mounting of the "
"root file system to read-only. Please consider moving it into an initrd file "
"system instead.", pid, strna(comm));
@@ -99,7 +98,7 @@ static void wait_for_children(Set *pids, sigset_t *mask) {
if (set_isempty(pids))
return;
- until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC;
+ until = now(CLOCK_MONOTONIC) + DEFAULT_TIMEOUT_USEC;
for (;;) {
struct timespec ts;
int k;
diff --git a/src/core/kmod-setup.c b/src/core/kmod-setup.c
index 3503db52ed..fd1021f706 100644
--- a/src/core/kmod-setup.c
+++ b/src/core/kmod-setup.c
@@ -64,9 +64,6 @@ int kmod_setup(void) {
/* this should never be a module */
{ "unix", "/proc/net/unix", true, true, NULL },
- /* IPC is needed before we bring up any other services */
- { "kdbus", "/sys/fs/kdbus", false, false, is_kdbus_wanted },
-
#ifdef HAVE_LIBIPTC
/* netfilter is needed by networkd, nspawn among others, and cannot be autoloaded */
{ "ip_tables", "/proc/net/ip_tables_names", false, false, NULL },
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index eb58586523..396f847213 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -19,9 +19,9 @@ m4_dnl Define the context options only once
m4_define(`EXEC_CONTEXT_CONFIG_ITEMS',
`$1.WorkingDirectory, config_parse_working_directory, 0, offsetof($1, exec_context)
$1.RootDirectory, config_parse_unit_path_printf, 0, offsetof($1, exec_context.root_directory)
-$1.User, config_parse_unit_string_printf, 0, offsetof($1, exec_context.user)
-$1.Group, config_parse_unit_string_printf, 0, offsetof($1, exec_context.group)
-$1.SupplementaryGroups, config_parse_strv, 0, offsetof($1, exec_context.supplementary_groups)
+$1.User, config_parse_user_group, 0, offsetof($1, exec_context.user)
+$1.Group, config_parse_user_group, 0, offsetof($1, exec_context.group)
+$1.SupplementaryGroups, config_parse_user_group_strv, 0, offsetof($1, exec_context.supplementary_groups)
$1.Nice, config_parse_exec_nice, 0, offsetof($1, exec_context)
$1.OOMScoreAdjust, config_parse_exec_oom_score_adjust, 0, offsetof($1, exec_context)
$1.IOSchedulingClass, config_parse_exec_io_class, 0, offsetof($1, exec_context)
@@ -34,6 +34,7 @@ $1.UMask, config_parse_mode, 0,
$1.Environment, config_parse_environ, 0, offsetof($1, exec_context.environment)
$1.EnvironmentFile, config_parse_unit_env_file, 0, offsetof($1, exec_context.environment_files)
$1.PassEnvironment, config_parse_pass_environ, 0, offsetof($1, exec_context.pass_environment)
+$1.DynamicUser, config_parse_bool, 0, offsetof($1, exec_context.dynamic_user)
$1.StandardInput, config_parse_input, 0, offsetof($1, exec_context.std_input)
$1.StandardOutput, config_parse_output, 0, offsetof($1, exec_context.std_output)
$1.StandardError, config_parse_output, 0, offsetof($1, exec_context.std_error)
@@ -56,11 +57,13 @@ m4_ifdef(`HAVE_SECCOMP',
$1.SystemCallArchitectures, config_parse_syscall_archs, 0, offsetof($1, exec_context.syscall_archs)
$1.SystemCallErrorNumber, config_parse_syscall_errno, 0, offsetof($1, exec_context)
$1.MemoryDenyWriteExecute, config_parse_bool, 0, offsetof($1, exec_context.memory_deny_write_execute)
+$1.RestrictRealtime, config_parse_bool, 0, offsetof($1, exec_context.restrict_realtime)
$1.RestrictAddressFamilies, config_parse_address_families, 0, offsetof($1, exec_context)',
`$1.SystemCallFilter, config_parse_warn_compat, DISABLED_CONFIGURATION, 0
$1.SystemCallArchitectures, config_parse_warn_compat, DISABLED_CONFIGURATION, 0
$1.SystemCallErrorNumber, config_parse_warn_compat, DISABLED_CONFIGURATION, 0
$1.MemoryDenyWriteExecute, config_parse_warn_compat, DISABLED_CONFIGURATION, 0
+$1.RestrictRealtime, config_parse_warn_compat, DISABLED_CONFIGURATION, 0
$1.RestrictAddressFamilies, config_parse_warn_compat, DISABLED_CONFIGURATION, 0')
$1.LimitCPU, config_parse_limit, RLIMIT_CPU, offsetof($1, exec_context.rlimit)
$1.LimitFSIZE, config_parse_limit, RLIMIT_FSIZE, offsetof($1, exec_context.rlimit)
@@ -78,9 +81,12 @@ $1.LimitMSGQUEUE, config_parse_limit, RLIMIT_MSGQ
$1.LimitNICE, config_parse_limit, RLIMIT_NICE, offsetof($1, exec_context.rlimit)
$1.LimitRTPRIO, config_parse_limit, RLIMIT_RTPRIO, offsetof($1, exec_context.rlimit)
$1.LimitRTTIME, config_parse_limit, RLIMIT_RTTIME, offsetof($1, exec_context.rlimit)
-$1.ReadWriteDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_dirs)
-$1.ReadOnlyDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_dirs)
-$1.InaccessibleDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_dirs)
+$1.ReadWriteDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_paths)
+$1.ReadOnlyDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_paths)
+$1.InaccessibleDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_paths)
+$1.ReadWritePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_paths)
+$1.ReadOnlyPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_paths)
+$1.InaccessiblePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_paths)
$1.PrivateTmp, config_parse_bool, 0, offsetof($1, exec_context.private_tmp)
$1.PrivateNetwork, config_parse_bool, 0, offsetof($1, exec_context.private_network)
$1.PrivateDevices, config_parse_bool, 0, offsetof($1, exec_context.private_devices)
@@ -280,13 +286,14 @@ Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC
Socket.ExecStopPre, config_parse_exec, SOCKET_EXEC_STOP_PRE, offsetof(Socket, exec_command)
Socket.ExecStopPost, config_parse_exec, SOCKET_EXEC_STOP_POST, offsetof(Socket, exec_command)
Socket.TimeoutSec, config_parse_sec, 0, offsetof(Socket, timeout_usec)
-Socket.SocketUser, config_parse_unit_string_printf, 0, offsetof(Socket, user)
-Socket.SocketGroup, config_parse_unit_string_printf, 0, offsetof(Socket, group)
+Socket.SocketUser, config_parse_user_group, 0, offsetof(Socket, user)
+Socket.SocketGroup, config_parse_user_group, 0, offsetof(Socket, group)
Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode)
Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode)
Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept)
Socket.Writable, config_parse_bool, 0, offsetof(Socket, writable)
Socket.MaxConnections, config_parse_unsigned, 0, offsetof(Socket, max_connections)
+Socket.MaxConnectionsPerSource, config_parse_unsigned, 0, offsetof(Socket, max_connections_per_source)
Socket.KeepAlive, config_parse_bool, 0, offsetof(Socket, keep_alive)
Socket.KeepAliveTimeSec, config_parse_sec, 0, offsetof(Socket, keep_alive_time)
Socket.KeepAliveIntervalSec, config_parse_sec, 0, offsetof(Socket, keep_alive_interval)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index b53301a147..d5f035b67f 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -64,6 +64,7 @@
#include "unit-name.h"
#include "unit-printf.h"
#include "unit.h"
+#include "user-util.h"
#include "utf8.h"
#include "web-util.h"
@@ -596,7 +597,7 @@ int config_parse_exec(
p = rvalue;
do {
_cleanup_free_ char *path = NULL, *firstword = NULL;
- bool separate_argv0 = false, ignore = false;
+ bool separate_argv0 = false, ignore = false, privileged = false;
_cleanup_free_ ExecCommand *nce = NULL;
_cleanup_strv_free_ char **n = NULL;
size_t nlen = 0, nbufsize = 0;
@@ -610,14 +611,18 @@ int config_parse_exec(
return 0;
f = firstword;
- for (i = 0; i < 2; i++) {
- /* We accept an absolute path as first argument, or
- * alternatively an absolute prefixed with @ to allow
- * overriding of argv[0]. */
+ for (i = 0; i < 3; i++) {
+ /* We accept an absolute path as first argument.
+ * If it's prefixed with - and the path doesn't exist,
+ * we ignore it instead of erroring out;
+ * if it's prefixed with @, we allow overriding of argv[0];
+ * and if it's prefixed with !, it will be run with full privileges */
if (*f == '-' && !ignore)
ignore = true;
else if (*f == '@' && !separate_argv0)
separate_argv0 = true;
+ else if (*f == '+' && !privileged)
+ privileged = true;
else
break;
f++;
@@ -715,6 +720,7 @@ int config_parse_exec(
nce->argv = n;
nce->path = path;
nce->ignore = ignore;
+ nce->privileged = privileged;
exec_command_append_list(e, nce);
@@ -1758,6 +1764,123 @@ int config_parse_sec_fix_0(
return 0;
}
+int config_parse_user_group(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char **user = data, *n;
+ Unit *u = userdata;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(u);
+
+ if (isempty(rvalue))
+ n = NULL;
+ else {
+ _cleanup_free_ char *k = NULL;
+
+ r = unit_full_printf(u, rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", rvalue);
+ return 0;
+ }
+
+ if (!valid_user_group_name_or_id(k)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k);
+ return 0;
+ }
+
+ n = k;
+ k = NULL;
+ }
+
+ free(*user);
+ *user = n;
+
+ return 0;
+}
+
+int config_parse_user_group_strv(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ char ***users = data;
+ Unit *u = userdata;
+ const char *p;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(u);
+
+ if (isempty(rvalue)) {
+ char **empty;
+
+ empty = new0(char*, 1);
+ if (!empty)
+ return log_oom();
+
+ strv_free(*users);
+ *users = empty;
+
+ return 0;
+ }
+
+ p = rvalue;
+ for (;;) {
+ _cleanup_free_ char *word = NULL, *k = NULL;
+
+ r = extract_first_word(&p, &word, WHITESPACE, 0);
+ if (r == 0)
+ break;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
+ break;
+ }
+
+ r = unit_full_printf(u, word, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", word);
+ continue;
+ }
+
+ if (!valid_user_group_name_or_id(k)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid user/group name or numeric ID, ignoring: %s", k);
+ continue;
+ }
+
+ r = strv_push(users, k);
+ if (r < 0)
+ return log_oom();
+
+ k = NULL;
+ }
+
+ return 0;
+}
+
int config_parse_busname_service(
const char *unit,
const char *filename,
@@ -2424,7 +2547,7 @@ static int syscall_filter_parse_one(
int id;
id = seccomp_syscall_resolve_name(t);
- if (id < 0) {
+ if (id == __NR_SCMP_ERROR) {
if (warn)
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse system call, ignoring: %s", t);
return 0;
@@ -2769,7 +2892,7 @@ int config_parse_cpu_quota(
void *userdata) {
CGroupContext *c = data;
- double percent;
+ int r;
assert(filename);
assert(lvalue);
@@ -2780,18 +2903,13 @@ int config_parse_cpu_quota(
return 0;
}
- if (!endswith(rvalue, "%")) {
- log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' not ending in '%%'. Ignoring.", rvalue);
+ r = parse_percent_unbounded(rvalue);
+ if (r <= 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "CPU quota '%s' invalid. Ignoring.", rvalue);
return 0;
}
- if (sscanf(rvalue, "%lf%%", &percent) != 1 || percent <= 0) {
- log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' invalid. Ignoring.", rvalue);
- return 0;
- }
-
- c->cpu_quota_per_sec_usec = (usec_t) (percent * USEC_PER_SEC / 100);
-
+ c->cpu_quota_per_sec_usec = ((usec_t) r * USEC_PER_SEC) / 100U;
return 0;
}
@@ -2812,9 +2930,19 @@ int config_parse_memory_limit(
int r;
if (!isempty(rvalue) && !streq(rvalue, "infinity")) {
- r = parse_size(rvalue, 1024, &bytes);
- if (r < 0 || bytes < 1) {
- log_syntax(unit, LOG_ERR, filename, line, r, "Memory limit '%s' invalid. Ignoring.", rvalue);
+
+ r = parse_percent(rvalue);
+ if (r < 0) {
+ r = parse_size(rvalue, 1024, &bytes);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Memory limit '%s' invalid. Ignoring.", rvalue);
+ return 0;
+ }
+ } else
+ bytes = physical_memory_scale(r, 100U);
+
+ if (bytes <= 0 || bytes >= UINT64_MAX) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Memory limit '%s' out of range. Ignoring.", rvalue);
return 0;
}
}
@@ -2851,9 +2979,18 @@ int config_parse_tasks_max(
return 0;
}
- r = safe_atou64(rvalue, &u);
- if (r < 0 || u < 1) {
- log_syntax(unit, LOG_ERR, filename, line, r, "Maximum tasks value '%s' invalid. Ignoring.", rvalue);
+ r = parse_percent(rvalue);
+ if (r < 0) {
+ r = safe_atou64(rvalue, &u);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Maximum tasks value '%s' invalid. Ignoring.", rvalue);
+ return 0;
+ }
+ } else
+ u = system_tasks_max_scale(r, 100U);
+
+ if (u <= 0 || u >= UINT64_MAX) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Maximum tasks value '%s' out of range. Ignoring.", rvalue);
return 0;
}
@@ -3584,7 +3721,7 @@ int config_parse_protect_home(
assert(data);
/* Our enum shall be a superset of booleans, hence first try
- * to parse as as boolean, and then as enum */
+ * to parse as boolean, and then as enum */
k = parse_boolean(rvalue);
if (k > 0)
@@ -3627,7 +3764,7 @@ int config_parse_protect_system(
assert(data);
/* Our enum shall be a superset of booleans, hence first try
- * to parse as as boolean, and then as enum */
+ * to parse as boolean, and then as enum */
k = parse_boolean(rvalue);
if (k > 0)
@@ -3743,7 +3880,7 @@ static int merge_by_names(Unit **u, Set *names, const char *id) {
/* If the symlink name we are looking at is unit template, then
we must search for instance of this template */
- if (unit_name_is_valid(k, UNIT_NAME_TEMPLATE)) {
+ if (unit_name_is_valid(k, UNIT_NAME_TEMPLATE) && (*u)->instance) {
_cleanup_free_ char *instance = NULL;
r = unit_name_replace_instance(k, (*u)->instance, &instance);
@@ -3825,7 +3962,15 @@ static int load_from_path(Unit *u, const char *path) {
if (r >= 0)
break;
filename = mfree(filename);
- if (r != -ENOENT)
+
+ /* ENOENT means that the file is missing or is a dangling symlink.
+ * ENOTDIR means that one of paths we expect to be is a directory
+ * is not a directory, we should just ignore that.
+ * EACCES means that the directory or file permissions are wrong.
+ */
+ if (r == -EACCES)
+ log_debug_errno(r, "Cannot access \"%s\": %m", filename);
+ else if (!IN_SET(r, -ENOENT, -ENOTDIR))
return r;
/* Empty the symlink names for the next run */
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index b36a2e3a02..213bce55a7 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -111,6 +111,8 @@ int config_parse_exec_utmp_mode(const char *unit, const char *filename, unsigned
int config_parse_working_directory(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_fdname(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_sec_fix_0(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_user_group(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_user_group_strv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length);
diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c
index 0145fe2894..76dfcfa6d7 100644
--- a/src/core/machine-id-setup.c
+++ b/src/core/machine-id-setup.c
@@ -17,11 +17,8 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
-#include <errno.h>
#include <fcntl.h>
#include <sched.h>
-#include <stdio.h>
-#include <string.h>
#include <sys/mount.h>
#include <unistd.h>
@@ -29,10 +26,8 @@
#include "alloc-util.h"
#include "fd-util.h"
-#include "fileio.h"
#include "fs-util.h"
-#include "hexdecoct.h"
-#include "io-util.h"
+#include "id128-util.h"
#include "log.h"
#include "machine-id-setup.h"
#include "macro.h"
@@ -46,101 +41,23 @@
#include "util.h"
#include "virt.h"
-static int shorten_uuid(char destination[34], const char source[36]) {
- unsigned i, j;
-
- assert(destination);
- assert(source);
-
- /* Converts a UUID into a machine ID, by lowercasing it and
- * removing dashes. Validates everything. */
-
- for (i = 0, j = 0; i < 36 && j < 32; i++) {
- int t;
-
- t = unhexchar(source[i]);
- if (t < 0)
- continue;
-
- destination[j++] = hexchar(t);
- }
-
- if (i != 36 || j != 32)
- return -EINVAL;
-
- destination[32] = '\n';
- destination[33] = 0;
- return 0;
-}
-
-static int read_machine_id(int fd, char id[34]) {
- char id_to_validate[34];
- int r;
-
- assert(fd >= 0);
- assert(id);
-
- /* Reads a machine ID from a file, validates it, and returns
- * it. The returned ID ends in a newline. */
-
- r = loop_read_exact(fd, id_to_validate, 33, false);
- if (r < 0)
- return r;
-
- if (id_to_validate[32] != '\n')
- return -EINVAL;
-
- id_to_validate[32] = 0;
-
- if (!id128_is_valid(id_to_validate))
- return -EINVAL;
-
- memcpy(id, id_to_validate, 32);
- id[32] = '\n';
- id[33] = 0;
- return 0;
-}
-
-static int write_machine_id(int fd, const char id[34]) {
- int r;
-
- assert(fd >= 0);
- assert(id);
-
- if (lseek(fd, 0, SEEK_SET) < 0)
- return -errno;
-
- r = loop_write(fd, id, 33, false);
- if (r < 0)
- return r;
-
- if (fsync(fd) < 0)
- return -errno;
-
- return 0;
-}
-
-static int generate_machine_id(char id[34], const char *root) {
- int fd, r;
- unsigned char *p;
- sd_id128_t buf;
- char *q;
+static int generate_machine_id(const char *root, sd_id128_t *ret) {
const char *dbus_machine_id;
+ _cleanup_close_ int fd = -1;
+ int r;
- assert(id);
-
- dbus_machine_id = prefix_roota(root, "/var/lib/dbus/machine-id");
+ assert(ret);
/* First, try reading the D-Bus machine id, unless it is a symlink */
+ dbus_machine_id = prefix_roota(root, "/var/lib/dbus/machine-id");
fd = open(dbus_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
if (fd >= 0) {
- r = read_machine_id(fd, id);
- safe_close(fd);
-
- if (r >= 0) {
+ if (id128_read_fd(fd, ID128_PLAIN, ret) >= 0) {
log_info("Initializing machine ID from D-Bus machine ID.");
return 0;
}
+
+ fd = safe_close(fd);
}
if (isempty(root)) {
@@ -151,13 +68,10 @@ static int generate_machine_id(char id[34], const char *root) {
if (detect_container() > 0) {
_cleanup_free_ char *e = NULL;
- r = getenv_for_pid(1, "container_uuid", &e);
- if (r > 0) {
- r = shorten_uuid(id, e);
- if (r >= 0) {
- log_info("Initializing machine ID from container UUID.");
- return 0;
- }
+ if (getenv_for_pid(1, "container_uuid", &e) > 0 &&
+ sd_id128_from_string(e, ret) >= 0) {
+ log_info("Initializing machine ID from container UUID.");
+ return 0;
}
} else if (detect_vm() == VIRTUALIZATION_KVM) {
@@ -166,51 +80,29 @@ static int generate_machine_id(char id[34], const char *root) {
* running in qemu/kvm and a machine ID was passed in
* via -uuid on the qemu/kvm command line */
- char uuid[36];
-
- fd = open("/sys/class/dmi/id/product_uuid", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
- if (fd >= 0) {
- r = loop_read_exact(fd, uuid, 36, false);
- safe_close(fd);
-
- if (r >= 0) {
- r = shorten_uuid(id, uuid);
- if (r >= 0) {
- log_info("Initializing machine ID from KVM UUID.");
- return 0;
- }
- }
+ if (id128_read("/sys/class/dmi/id/product_uuid", ID128_UUID, ret) >= 0) {
+ log_info("Initializing machine ID from KVM UUID.");
+ return 0;
}
}
}
/* If that didn't work, generate a random machine id */
- r = sd_id128_randomize(&buf);
+ r = sd_id128_randomize(ret);
if (r < 0)
- return log_error_errno(r, "Failed to open /dev/urandom: %m");
-
- for (p = buf.bytes, q = id; p < buf.bytes + sizeof(buf); p++, q += 2) {
- q[0] = hexchar(*p >> 4);
- q[1] = hexchar(*p & 15);
- }
-
- id[32] = '\n';
- id[33] = 0;
+ return log_error_errno(r, "Failed to generate randomized : %m");
log_info("Initializing machine ID from random generator.");
-
return 0;
}
-int machine_id_setup(const char *root, sd_id128_t machine_id) {
+int machine_id_setup(const char *root, sd_id128_t machine_id, sd_id128_t *ret) {
const char *etc_machine_id, *run_machine_id;
_cleanup_close_ int fd = -1;
- bool writable = true;
- char id[34]; /* 32 + \n + \0 */
+ bool writable;
int r;
etc_machine_id = prefix_roota(root, "/etc/machine-id");
- run_machine_id = prefix_roota(root, "/run/machine-id");
RUN_WITH_UMASK(0000) {
/* We create this 0444, to indicate that this isn't really
@@ -218,7 +110,7 @@ int machine_id_setup(const char *root, sd_id128_t machine_id) {
* will be owned by root it doesn't matter much, but maybe
* people look. */
- mkdir_parents(etc_machine_id, 0755);
+ (void) mkdir_parents(etc_machine_id, 0755);
fd = open(etc_machine_id, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444);
if (fd < 0) {
int old_errno = errno;
@@ -239,41 +131,41 @@ int machine_id_setup(const char *root, sd_id128_t machine_id) {
}
writable = false;
- }
+ } else
+ writable = true;
}
- /* A machine id argument overrides all other machined-ids */
- if (!sd_id128_is_null(machine_id)) {
- sd_id128_to_string(machine_id, id);
- id[32] = '\n';
- id[33] = 0;
- } else {
- if (read_machine_id(fd, id) >= 0)
- return 0;
+ /* A we got a valid machine ID argument, that's what counts */
+ if (sd_id128_is_null(machine_id)) {
- /* Hmm, so, the id currently stored is not useful, then let's
- * generate one */
+ /* Try to read any existing machine ID */
+ if (id128_read_fd(fd, ID128_PLAIN, ret) >= 0)
+ return 0;
- r = generate_machine_id(id, root);
+ /* Hmm, so, the id currently stored is not useful, then let's generate one */
+ r = generate_machine_id(root, &machine_id);
if (r < 0)
return r;
+
+ if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
+ return log_error_errno(errno, "Failed to seek: %m");
}
if (writable)
- if (write_machine_id(fd, id) >= 0)
- return 0;
+ if (id128_write_fd(fd, ID128_PLAIN, machine_id, true) >= 0)
+ goto finish;
fd = safe_close(fd);
- /* Hmm, we couldn't write it? So let's write it to
- * /run/machine-id as a replacement */
+ /* Hmm, we couldn't write it? So let's write it to /run/machine-id as a replacement */
- RUN_WITH_UMASK(0022) {
- r = write_string_file(run_machine_id, id, WRITE_STRING_FILE_CREATE);
- if (r < 0) {
- (void) unlink(run_machine_id);
- return log_error_errno(r, "Cannot write %s: %m", run_machine_id);
- }
+ run_machine_id = prefix_roota(root, "/run/machine-id");
+
+ RUN_WITH_UMASK(0022)
+ r = id128_write(run_machine_id, ID128_PLAIN, machine_id, false);
+ if (r < 0) {
+ (void) unlink(run_machine_id);
+ return log_error_errno(r, "Cannot write %s: %m", run_machine_id);
}
/* And now, let's mount it over */
@@ -286,7 +178,11 @@ int machine_id_setup(const char *root, sd_id128_t machine_id) {
/* Mark the mount read-only */
if (mount(NULL, etc_machine_id, NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, NULL) < 0)
- log_warning_errno(errno, "Failed to make transient %s read-only: %m", etc_machine_id);
+ log_warning_errno(errno, "Failed to make transient %s read-only, ignoring: %m", etc_machine_id);
+
+finish:
+ if (ret)
+ *ret = machine_id;
return 0;
}
@@ -294,16 +190,20 @@ int machine_id_setup(const char *root, sd_id128_t machine_id) {
int machine_id_commit(const char *root) {
_cleanup_close_ int fd = -1, initial_mntns_fd = -1;
const char *etc_machine_id;
- char id[34]; /* 32 + \n + \0 */
+ sd_id128_t id;
int r;
+ /* Replaces a tmpfs bind mount of /etc/machine-id by a proper file, atomically. For this, the umount is removed
+ * in a mount namespace, a new file is created at the right place. Afterwards the mount is also removed in the
+ * original mount namespace, thus revealing the file that was just created. */
+
etc_machine_id = prefix_roota(root, "/etc/machine-id");
r = path_is_mount_point(etc_machine_id, 0);
if (r < 0)
return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id);
if (r == 0) {
- log_debug("%s is is not a mount point. Nothing to do.", etc_machine_id);
+ log_debug("%s is not a mount point. Nothing to do.", etc_machine_id);
return 0;
}
@@ -312,10 +212,6 @@ int machine_id_commit(const char *root) {
if (fd < 0)
return log_error_errno(errno, "Cannot open %s: %m", etc_machine_id);
- r = read_machine_id(fd, id);
- if (r < 0)
- return log_error_errno(r, "We didn't find a valid machine ID in %s.", etc_machine_id);
-
r = fd_is_temporary_fs(fd);
if (r < 0)
return log_error_errno(r, "Failed to determine whether %s is on a temporary file system: %m", etc_machine_id);
@@ -324,6 +220,10 @@ int machine_id_commit(const char *root) {
return -EROFS;
}
+ r = id128_read_fd(fd, ID128_PLAIN, &id);
+ if (r < 0)
+ return log_error_errno(r, "We didn't find a valid machine ID in %s.", etc_machine_id);
+
fd = safe_close(fd);
/* Store current mount namespace */
@@ -342,15 +242,9 @@ int machine_id_commit(const char *root) {
return log_error_errno(errno, "Failed to unmount transient %s file in our private namespace: %m", etc_machine_id);
/* Update a persistent version of etc_machine_id */
- fd = open(etc_machine_id, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444);
- if (fd < 0)
- return log_error_errno(errno, "Cannot open for writing %s. This is mandatory to get a persistent machine-id: %m", etc_machine_id);
-
- r = write_machine_id(fd, id);
+ r = id128_write(etc_machine_id, ID128_PLAIN, id, true);
if (r < 0)
- return log_error_errno(r, "Cannot write %s: %m", etc_machine_id);
-
- fd = safe_close(fd);
+ return log_error_errno(r, "Cannot write %s. This is mandatory to get a persistent machine ID: %m", etc_machine_id);
/* Return to initial namespace and proceed a lazy tmpfs unmount */
r = namespace_enter(-1, initial_mntns_fd, -1, -1, -1);
diff --git a/src/core/machine-id-setup.h b/src/core/machine-id-setup.h
index a7e7678ed9..29f4620646 100644
--- a/src/core/machine-id-setup.h
+++ b/src/core/machine-id-setup.h
@@ -20,4 +20,4 @@
***/
int machine_id_commit(const char *root);
-int machine_id_setup(const char *root, sd_id128_t machine_id);
+int machine_id_setup(const char *root, sd_id128_t requested, sd_id128_t *ret);
diff --git a/src/core/macros.systemd.in b/src/core/macros.systemd.in
index 2cace3d3ba..6e8a3b3e3d 100644
--- a/src/core/macros.systemd.in
+++ b/src/core/macros.systemd.in
@@ -29,6 +29,8 @@
%_sysusersdir @sysusersdir@
%_sysctldir @sysctldir@
%_binfmtdir @binfmtdir@
+%_systemdgeneratordir @systemgeneratordir@
+%_systemdusergeneratordir @usergeneratordir@
%systemd_requires \
Requires(post): systemd \
@@ -36,6 +38,12 @@ Requires(preun): systemd \
Requires(postun): systemd \
%{nil}
+%systemd_ordering \
+OrderWithRequires(post): systemd \
+OrderWithRequires(preun): systemd \
+OrderWithRequires(postun): systemd \
+%{nil}
+
%systemd_post() \
if [ $1 -eq 1 ] ; then \
# Initial installation \
diff --git a/src/core/main.c b/src/core/main.c
index 93098daa9b..094bbef964 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -92,8 +92,7 @@ static enum {
ACTION_HELP,
ACTION_VERSION,
ACTION_TEST,
- ACTION_DUMP_CONFIGURATION_ITEMS,
- ACTION_DONE
+ ACTION_DUMP_CONFIGURATION_ITEMS
} arg_action = ACTION_RUN;
static char *arg_default_unit = NULL;
static bool arg_system = false;
@@ -127,7 +126,7 @@ static bool arg_default_io_accounting = false;
static bool arg_default_blockio_accounting = false;
static bool arg_default_memory_accounting = false;
static bool arg_default_tasks_accounting = true;
-static uint64_t arg_default_tasks_max = UINT64_C(512);
+static uint64_t arg_default_tasks_max = UINT64_MAX;
static sd_id128_t arg_machine_id = {};
noreturn static void freeze_or_reboot(void) {
@@ -291,14 +290,16 @@ static int parse_crash_chvt(const char *value) {
}
static int set_machine_id(const char *m) {
+ sd_id128_t t;
assert(m);
- if (sd_id128_from_string(m, &arg_machine_id) < 0)
+ if (sd_id128_from_string(m, &t) < 0)
return -EINVAL;
- if (sd_id128_is_null(arg_machine_id))
+ if (sd_id128_is_null(t))
return -EINVAL;
+ arg_machine_id = t;
return 0;
}
@@ -409,7 +410,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value) {
if (detect_container() > 0)
log_set_target(LOG_TARGET_CONSOLE);
- } else if (!in_initrd() && !value) {
+ } else if (!value) {
const char *target;
/* SysV compatibility */
@@ -1294,6 +1295,40 @@ static int bump_unix_max_dgram_qlen(void) {
return 1;
}
+static int fixup_environment(void) {
+ _cleanup_free_ char *term = NULL;
+ int r;
+
+ /* We expect the environment to be set correctly
+ * if run inside a container. */
+ if (detect_container() > 0)
+ return 0;
+
+ /* When started as PID1, the kernel uses /dev/console
+ * for our stdios and uses TERM=linux whatever the
+ * backend device used by the console. We try to make
+ * a better guess here since some consoles might not
+ * have support for color mode for example.
+ *
+ * However if TERM was configured through the kernel
+ * command line then leave it alone. */
+
+ r = get_proc_cmdline_key("TERM=", &term);
+ if (r < 0)
+ return r;
+
+ if (r == 0) {
+ term = strdup(default_term_for_tty("/dev/console"));
+ if (!term)
+ return -ENOMEM;
+ }
+
+ if (setenv("TERM", term, 1) < 0)
+ return -errno;
+
+ return 0;
+}
+
int main(int argc, char *argv[]) {
Manager *m = NULL;
int r, retval = EXIT_FAILURE;
@@ -1353,7 +1388,6 @@ int main(int argc, char *argv[]) {
saved_argv = argv;
saved_argc = argc;
- log_show_color(colors_enabled());
log_set_upgrade_syslog_to_journal(true);
/* Disable the umask logic */
@@ -1364,7 +1398,6 @@ int main(int argc, char *argv[]) {
/* Running outside of a container as PID 1 */
arg_system = true;
- make_null_stdio();
log_set_target(LOG_TARGET_KMSG);
log_open();
@@ -1381,12 +1414,12 @@ int main(int argc, char *argv[]) {
if (mac_selinux_setup(&loaded_policy) < 0) {
error_message = "Failed to load SELinux policy";
goto finish;
- } else if (ima_setup() < 0) {
- error_message = "Failed to load IMA policy";
- goto finish;
} else if (mac_smack_setup(&loaded_policy) < 0) {
error_message = "Failed to load SMACK policy";
goto finish;
+ } else if (ima_setup() < 0) {
+ error_message = "Failed to load IMA policy";
+ goto finish;
}
dual_timestamp_get(&security_finish_timestamp);
}
@@ -1417,7 +1450,7 @@ int main(int argc, char *argv[]) {
/*
* Do a dummy very first call to seal the kernel's time warp magic.
*
- * Do not call this this from inside the initrd. The initrd might not
+ * Do not call this from inside the initrd. The initrd might not
* carry /etc/adjtime with LOCAL, but the real system could be set up
* that way. In such case, we need to delay the time-warp or the sealing
* until we reach the real system.
@@ -1480,6 +1513,19 @@ int main(int argc, char *argv[]) {
(void) write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0);
}
+ if (arg_system) {
+ if (fixup_environment() < 0) {
+ error_message = "Failed to fix up PID1 environment";
+ goto finish;
+ }
+
+ /* Try to figure out if we can use colors with the console. No
+ * need to do that for user instances since they never log
+ * into the console. */
+ log_show_color(colors_enabled());
+ make_null_stdio();
+ }
+
/* Initialize default unit */
r = free_and_strdup(&arg_default_unit, SPECIAL_DEFAULT_TARGET);
if (r < 0) {
@@ -1513,6 +1559,8 @@ int main(int argc, char *argv[]) {
(void) reset_all_signal_handlers();
(void) ignore_signals(SIGNALS_IGNORE, -1);
+ arg_default_tasks_max = system_tasks_max_scale(15U, 100U); /* 15% the system PIDs equals 4915 by default. */
+
if (parse_config_file() < 0) {
error_message = "Failed to parse config file";
goto finish;
@@ -1569,9 +1617,6 @@ int main(int argc, char *argv[]) {
unit_dump_config_items(stdout);
retval = EXIT_SUCCESS;
goto finish;
- } else if (arg_action == ACTION_DONE) {
- retval = EXIT_SUCCESS;
- goto finish;
}
if (!arg_system &&
@@ -1674,7 +1719,7 @@ int main(int argc, char *argv[]) {
status_welcome();
hostname_setup();
- machine_id_setup(NULL, arg_machine_id);
+ machine_id_setup(NULL, arg_machine_id, NULL);
loopback_setup();
bump_unix_max_dgram_qlen();
@@ -1967,6 +2012,9 @@ finish:
log_error_errno(r, "Failed to switch root, trying to continue: %m");
}
+ /* Reopen the console */
+ (void) make_console_stdio();
+
args_size = MAX(6, argc+1);
args = newa(const char*, args_size);
@@ -1992,10 +2040,6 @@ finish:
args[i++] = sfd;
args[i++] = NULL;
- /* do not pass along the environment we inherit from the kernel or initrd */
- if (switch_root_dir)
- (void) clearenv();
-
assert(i <= args_size);
/*
@@ -2018,9 +2062,6 @@ finish:
arg_serialization = safe_fclose(arg_serialization);
fds = fdset_free(fds);
- /* Reopen the console */
- (void) make_console_stdio();
-
for (j = 1, i = 1; j < (unsigned) argc; j++)
args[i++] = argv[j];
args[i++] = NULL;
diff --git a/src/core/manager.c b/src/core/manager.c
index 14d97a87d0..c20e185d78 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -64,7 +64,6 @@
#include "manager.h"
#include "missing.h"
#include "mkdir.h"
-#include "mkdir.h"
#include "parse-util.h"
#include "path-lookup.h"
#include "path-util.h"
@@ -136,23 +135,28 @@ static void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned po
if (pos > 1) {
if (pos > 2)
p = mempset(p, ' ', pos-2);
- p = stpcpy(p, ANSI_RED);
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_RED);
*p++ = '*';
}
if (pos > 0 && pos <= width) {
- p = stpcpy(p, ANSI_HIGHLIGHT_RED);
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_HIGHLIGHT_RED);
*p++ = '*';
}
- p = stpcpy(p, ANSI_NORMAL);
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_NORMAL);
if (pos < width) {
- p = stpcpy(p, ANSI_RED);
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_RED);
*p++ = '*';
if (pos < width-1)
p = mempset(p, ' ', width-1-pos);
- strcpy(p, ANSI_NORMAL);
+ if (log_get_show_color())
+ strcpy(p, ANSI_NORMAL);
}
}
@@ -549,7 +553,6 @@ static int manager_default_environment(Manager *m) {
return 0;
}
-
int manager_new(UnitFileScope scope, bool test_run, Manager **_m) {
Manager *m;
int r;
@@ -565,7 +568,7 @@ int manager_new(UnitFileScope scope, bool test_run, Manager **_m) {
m->exit_code = _MANAGER_EXIT_CODE_INVALID;
m->default_timer_accuracy_usec = USEC_PER_MINUTE;
m->default_tasks_accounting = true;
- m->default_tasks_max = UINT64_C(512);
+ m->default_tasks_max = UINT64_MAX;
#ifdef ENABLE_EFI
if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0)
@@ -809,28 +812,6 @@ static int manager_setup_cgroups_agent(Manager *m) {
return 0;
}
-static int manager_setup_kdbus(Manager *m) {
- _cleanup_free_ char *p = NULL;
-
- assert(m);
-
- if (m->test_run || m->kdbus_fd >= 0)
- return 0;
- if (!is_kdbus_available())
- return -ESOCKTNOSUPPORT;
-
- m->kdbus_fd = bus_kernel_create_bus(
- MANAGER_IS_SYSTEM(m) ? "system" : "user",
- MANAGER_IS_SYSTEM(m), &p);
-
- if (m->kdbus_fd < 0)
- return log_debug_errno(m->kdbus_fd, "Failed to set up kdbus: %m");
-
- log_debug("Successfully set up kdbus on %s", p);
-
- return 0;
-}
-
static int manager_connect_bus(Manager *m, bool reexecuting) {
bool try_bus_connect;
@@ -872,6 +853,19 @@ enum {
_GC_OFFSET_MAX
};
+static void unit_gc_mark_good(Unit *u, unsigned gc_marker)
+{
+ Iterator i;
+ Unit *other;
+
+ u->gc_marker = gc_marker + GC_OFFSET_GOOD;
+
+ /* Recursively mark referenced units as GOOD as well */
+ SET_FOREACH(other, u->dependencies[UNIT_REFERENCES], i)
+ if (other->gc_marker == gc_marker + GC_OFFSET_UNSURE)
+ unit_gc_mark_good(other, gc_marker);
+}
+
static void unit_gc_sweep(Unit *u, unsigned gc_marker) {
Iterator i;
Unit *other;
@@ -881,6 +875,7 @@ static void unit_gc_sweep(Unit *u, unsigned gc_marker) {
if (u->gc_marker == gc_marker + GC_OFFSET_GOOD ||
u->gc_marker == gc_marker + GC_OFFSET_BAD ||
+ u->gc_marker == gc_marker + GC_OFFSET_UNSURE ||
u->gc_marker == gc_marker + GC_OFFSET_IN_PATH)
return;
@@ -921,7 +916,7 @@ bad:
return;
good:
- u->gc_marker = gc_marker + GC_OFFSET_GOOD;
+ unit_gc_mark_good(u, gc_marker);
}
static unsigned manager_dispatch_gc_queue(Manager *m) {
@@ -1008,6 +1003,9 @@ Manager* manager_free(Manager *m) {
bus_done(m);
+ dynamic_user_vacuum(m, false);
+ hashmap_free(m->dynamic_users);
+
hashmap_free(m->units);
hashmap_free(m->jobs);
hashmap_free(m->watch_pids1);
@@ -1225,13 +1223,15 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
/* We might have deserialized the kdbus control fd, but if we
* didn't, then let's create the bus now. */
- manager_setup_kdbus(m);
manager_connect_bus(m, !!serialization);
bus_track_coldplug(m, &m->subscribed, &m->deserialized_subscribed);
/* Third, fire things up! */
manager_coldplug(m);
+ /* Release any dynamic users no longer referenced */
+ dynamic_user_vacuum(m, true);
+
if (serialization) {
assert(m->n_reloading > 0);
m->n_reloading--;
@@ -1720,16 +1720,28 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t
}
static void invoke_sigchld_event(Manager *m, Unit *u, const siginfo_t *si) {
+ uint64_t iteration;
+
assert(m);
assert(u);
assert(si);
+ sd_event_get_iteration(m->event, &iteration);
+
log_unit_debug(u, "Child "PID_FMT" belongs to %s", si->si_pid, u->id);
unit_unwatch_pid(u, si->si_pid);
- if (UNIT_VTABLE(u)->sigchld_event)
- UNIT_VTABLE(u)->sigchld_event(u, si->si_pid, si->si_code, si->si_status);
+ if (UNIT_VTABLE(u)->sigchld_event) {
+ if (set_size(u->pids) <= 1 ||
+ iteration != u->sigchldgen ||
+ unit_main_pid(u) == si->si_pid ||
+ unit_control_pid(u) == si->si_pid) {
+ UNIT_VTABLE(u)->sigchld_event(u, si->si_pid, si->si_code, si->si_status);
+ u->sigchldgen = iteration;
+ } else
+ log_debug("%s already issued a sigchld this iteration %" PRIu64 ", skipping. Pids still being watched %d", u->id, iteration, set_size(u->pids));
+ }
}
static int manager_dispatch_sigchld(Manager *m) {
@@ -2396,6 +2408,10 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) {
bus_track_serialize(m->subscribed, f);
+ r = dynamic_user_serialize(m, f, fds);
+ if (r < 0)
+ return r;
+
fputc('\n', f);
HASHMAP_FOREACH_KEY(u, t, m->units, i) {
@@ -2572,7 +2588,9 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
m->kdbus_fd = fdset_remove(fds, fd);
}
- } else {
+ } else if (startswith(l, "dynamic-user="))
+ dynamic_user_deserialize_one(m, l + 13, fds);
+ else {
int k;
k = bus_track_deserialize_item(&m->deserialized_subscribed, l);
@@ -2653,6 +2671,7 @@ int manager_reload(Manager *m) {
manager_clear_jobs_and_units(m);
lookup_paths_flush_generator(&m->lookup_paths);
lookup_paths_free(&m->lookup_paths);
+ dynamic_user_vacuum(m, false);
q = lookup_paths_init(&m->lookup_paths, m->unit_file_scope, 0, NULL);
if (q < 0 && r >= 0)
@@ -2689,6 +2708,9 @@ int manager_reload(Manager *m) {
/* Third, fire things up! */
manager_coldplug(m);
+ /* Release any dynamic users no longer referenced */
+ dynamic_user_vacuum(m, true);
+
/* Sync current state of bus names with our set of listening units */
if (m->api_bus)
manager_sync_bus_names(m, m->api_bus);
diff --git a/src/core/manager.h b/src/core/manager.h
index 6ed15c1a41..c681d5dc46 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -298,6 +298,9 @@ struct Manager {
/* Used for processing polkit authorization responses */
Hashmap *polkit_registry;
+ /* Dynamic users/groups, indexed by their name */
+ Hashmap *dynamic_users;
+
/* When the user hits C-A-D more than 7 times per 2s, reboot immediately... */
RateLimit ctrl_alt_del_ratelimit;
diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c
index 40fc548b42..5d8ab0ec70 100644
--- a/src/core/mount-setup.c
+++ b/src/core/mount-setup.c
@@ -28,6 +28,7 @@
#include "cgroup-util.h"
#include "dev-setup.h"
#include "efivars.h"
+#include "fs-util.h"
#include "label.h"
#include "log.h"
#include "macro.h"
@@ -108,8 +109,6 @@ static const MountPoint mount_table[] = {
{ "efivarfs", "/sys/firmware/efi/efivars", "efivarfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
is_efi_boot, MNT_NONE },
#endif
- { "kdbusfs", "/sys/fs/kdbus", "kdbusfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV,
- is_kdbus_wanted, MNT_IN_CONTAINER },
};
/* These are API file systems that might be mounted by other software,
@@ -405,9 +404,16 @@ int mount_setup(bool loaded_policy) {
* really needs to stay for good, otherwise software that
* copied sd-daemon.c into their sources will misdetect
* systemd. */
- mkdir_label("/run/systemd", 0755);
- mkdir_label("/run/systemd/system", 0755);
- mkdir_label("/run/systemd/inaccessible", 0000);
+ (void) mkdir_label("/run/systemd", 0755);
+ (void) mkdir_label("/run/systemd/system", 0755);
+ (void) mkdir_label("/run/systemd/inaccessible", 0000);
+ /* Set up inaccessible items */
+ (void) mknod("/run/systemd/inaccessible/reg", S_IFREG | 0000, 0);
+ (void) mkdir_label("/run/systemd/inaccessible/dir", 0000);
+ (void) mknod("/run/systemd/inaccessible/chr", S_IFCHR | 0000, makedev(0, 0));
+ (void) mknod("/run/systemd/inaccessible/blk", S_IFBLK | 0000, makedev(0, 0));
+ (void) mkfifo("/run/systemd/inaccessible/fifo", 0000);
+ (void) mknod("/run/systemd/inaccessible/sock", S_IFSOCK | 0000, 0);
return 0;
}
diff --git a/src/core/mount.c b/src/core/mount.c
index 7b20892b0d..afb20af9e2 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -245,6 +245,8 @@ static void mount_done(Unit *u) {
exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX);
m->control_command = NULL;
+ dynamic_creds_unref(&m->dynamic_creds);
+
mount_unwatch_control_pid(m);
m->timer_event_source = sd_event_source_unref(m->timer_event_source);
@@ -650,6 +652,9 @@ static int mount_coldplug(Unit *u) {
return r;
}
+ if (!IN_SET(new_state, MOUNT_DEAD, MOUNT_FAILED))
+ (void) unit_setup_dynamic_creds(u);
+
mount_set_state(m, new_state);
return 0;
}
@@ -718,6 +723,10 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
if (r < 0)
return r;
+ r = unit_setup_dynamic_creds(UNIT(m));
+ if (r < 0)
+ return r;
+
r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec));
if (r < 0)
return r;
@@ -734,6 +743,7 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
&m->exec_context,
&exec_params,
m->exec_runtime,
+ &m->dynamic_creds,
&pid);
if (r < 0)
return r;
@@ -754,12 +764,14 @@ static void mount_enter_dead(Mount *m, MountResult f) {
if (f != MOUNT_SUCCESS)
m->result = f;
+ mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD);
+
exec_runtime_destroy(m->exec_runtime);
m->exec_runtime = exec_runtime_unref(m->exec_runtime);
exec_context_destroy_runtime_directory(&m->exec_context, manager_get_runtime_prefix(UNIT(m)->manager));
- mount_set_state(m, m->result != MOUNT_SUCCESS ? MOUNT_FAILED : MOUNT_DEAD);
+ dynamic_creds_destroy(&m->dynamic_creds);
}
static void mount_enter_mounted(Mount *m, MountResult f) {
@@ -1704,6 +1716,7 @@ static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents,
/* This has just been unmounted by
* somebody else, follow the state
* change. */
+ mount->result = MOUNT_SUCCESS; /* make sure we forget any earlier umount failures */
mount_enter_dead(mount, MOUNT_SUCCESS);
break;
@@ -1814,6 +1827,7 @@ const UnitVTable mount_vtable = {
.cgroup_context_offset = offsetof(Mount, cgroup_context),
.kill_context_offset = offsetof(Mount, kill_context),
.exec_runtime_offset = offsetof(Mount, exec_runtime),
+ .dynamic_creds_offset = offsetof(Mount, dynamic_creds),
.sections =
"Unit\0"
diff --git a/src/core/mount.h b/src/core/mount.h
index da529c44f4..ac27b518cc 100644
--- a/src/core/mount.h
+++ b/src/core/mount.h
@@ -21,8 +21,8 @@
typedef struct Mount Mount;
-#include "execute.h"
#include "kill.h"
+#include "dynamic-user.h"
typedef enum MountExecCommand {
MOUNT_EXEC_MOUNT,
@@ -85,6 +85,7 @@ struct Mount {
CGroupContext cgroup_context;
ExecRuntime *exec_runtime;
+ DynamicCreds dynamic_creds;
MountState state, deserialized_state;
diff --git a/src/core/namespace.c b/src/core/namespace.c
index 203d122810..52a2505d94 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -278,6 +278,7 @@ static int apply_mount(
const char *what;
int r;
+ struct stat target;
assert(m);
@@ -287,12 +288,21 @@ static int apply_mount(
/* First, get rid of everything that is below if there
* is anything... Then, overmount it with an
- * inaccessible directory. */
+ * inaccessible path. */
umount_recursive(m->path, 0);
- what = "/run/systemd/inaccessible";
- break;
+ if (lstat(m->path, &target) < 0) {
+ if (m->ignore && errno == ENOENT)
+ return 0;
+ return -errno;
+ }
+ what = mode_to_inaccessible_node(target.st_mode);
+ if (!what) {
+ log_debug("File type not supported for inaccessible mounts. Note that symlinks are not allowed");
+ return -ELOOP;
+ }
+ break;
case READONLY:
case READWRITE:
/* Nothing to mount here, we just later toggle the
@@ -317,12 +327,14 @@ static int apply_mount(
assert(what);
r = mount(what, m->path, NULL, MS_BIND|MS_REC, NULL);
- if (r >= 0)
+ if (r >= 0) {
log_debug("Successfully mounted %s to %s", what, m->path);
- else if (m->ignore && errno == ENOENT)
- return 0;
-
- return r;
+ return r;
+ } else {
+ if (m->ignore && errno == ENOENT)
+ return 0;
+ return log_debug_errno(errno, "Failed to mount %s to %s: %m", what, m->path);
+ }
}
static int make_read_only(BindMount *m) {
@@ -335,7 +347,8 @@ static int make_read_only(BindMount *m) {
else if (IN_SET(m->mode, READWRITE, PRIVATE_TMP, PRIVATE_VAR_TMP, PRIVATE_DEV)) {
r = bind_remount_recursive(m->path, false);
if (r == 0 && m->mode == PRIVATE_DEV) /* can be readonly but the submounts can't*/
- r = mount(NULL, m->path, NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL);
+ if (mount(NULL, m->path, NULL, MS_REMOUNT|DEV_MOUNT_OPTIONS|MS_RDONLY, NULL) < 0)
+ r = -errno;
} else
r = 0;
@@ -347,9 +360,9 @@ static int make_read_only(BindMount *m) {
int setup_namespace(
const char* root_directory,
- char** read_write_dirs,
- char** read_only_dirs,
- char** inaccessible_dirs,
+ char** read_write_paths,
+ char** read_only_paths,
+ char** inaccessible_paths,
const char* tmp_dir,
const char* var_tmp_dir,
bool private_dev,
@@ -368,9 +381,9 @@ int setup_namespace(
return -errno;
n = !!tmp_dir + !!var_tmp_dir +
- strv_length(read_write_dirs) +
- strv_length(read_only_dirs) +
- strv_length(inaccessible_dirs) +
+ strv_length(read_write_paths) +
+ strv_length(read_only_paths) +
+ strv_length(inaccessible_paths) +
private_dev +
(protect_home != PROTECT_HOME_NO ? 3 : 0) +
(protect_system != PROTECT_SYSTEM_NO ? 2 : 0) +
@@ -378,15 +391,15 @@ int setup_namespace(
if (n > 0) {
m = mounts = (BindMount *) alloca0(n * sizeof(BindMount));
- r = append_mounts(&m, read_write_dirs, READWRITE);
+ r = append_mounts(&m, read_write_paths, READWRITE);
if (r < 0)
return r;
- r = append_mounts(&m, read_only_dirs, READONLY);
+ r = append_mounts(&m, read_only_paths, READONLY);
if (r < 0)
return r;
- r = append_mounts(&m, inaccessible_dirs, INACCESSIBLE);
+ r = append_mounts(&m, inaccessible_paths, INACCESSIBLE);
if (r < 0)
return r;
@@ -629,7 +642,7 @@ int setup_netns(int netns_storage_socket[2]) {
}
fail:
- lockf(netns_storage_socket[0], F_ULOCK, 0);
+ (void) lockf(netns_storage_socket[0], F_ULOCK, 0);
return r;
}
diff --git a/src/core/namespace.h b/src/core/namespace.h
index b54b7b47d6..1aedf5f208 100644
--- a/src/core/namespace.h
+++ b/src/core/namespace.h
@@ -40,9 +40,9 @@ typedef enum ProtectSystem {
} ProtectSystem;
int setup_namespace(const char *chroot,
- char **read_write_dirs,
- char **read_only_dirs,
- char **inaccessible_dirs,
+ char **read_write_paths,
+ char **read_only_paths,
+ char **inaccessible_paths,
const char *tmp_dir,
const char *var_tmp_dir,
bool private_dev,
diff --git a/src/core/scope.c b/src/core/scope.c
index 238f63a729..b45e238974 100644
--- a/src/core/scope.c
+++ b/src/core/scope.c
@@ -240,7 +240,7 @@ static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) {
/* If we have a controller set let's ask the controller nicely
* to terminate the scope, instead of us going directly into
- * SIGTERM beserk mode */
+ * SIGTERM berserk mode */
if (state == SCOPE_STOP_SIGTERM)
skip_signal = bus_scope_send_request_stop(s) > 0;
@@ -248,7 +248,9 @@ static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) {
r = unit_kill_context(
UNIT(s),
&s->kill_context,
- state != SCOPE_STOP_SIGTERM ? KILL_KILL : KILL_TERMINATE,
+ state != SCOPE_STOP_SIGTERM ? KILL_KILL :
+ s->was_abandoned ? KILL_TERMINATE_AND_LOG :
+ KILL_TERMINATE,
-1, -1, false);
if (r < 0)
goto fail;
@@ -369,6 +371,7 @@ static int scope_serialize(Unit *u, FILE *f, FDSet *fds) {
assert(fds);
unit_serialize_item(u, f, "state", scope_state_to_string(s->state));
+ unit_serialize_item(u, f, "was-abandoned", yes_no(s->was_abandoned));
return 0;
}
@@ -389,6 +392,14 @@ static int scope_deserialize_item(Unit *u, const char *key, const char *value, F
else
s->deserialized_state = state;
+ } else if (streq(key, "was-abandoned")) {
+ int k;
+
+ k = parse_boolean(value);
+ if (k < 0)
+ log_unit_debug(u, "Failed to parse boolean value: %s", value);
+ else
+ s->was_abandoned = k;
} else
log_unit_debug(u, "Unknown serialization key: %s", key);
@@ -428,8 +439,9 @@ static void scope_sigchld_event(Unit *u, pid_t pid, int code, int status) {
unit_tidy_watch_pids(u, 0, 0);
unit_watch_all_pids(u);
- /* If the PID set is empty now, then let's finish this off */
- if (set_isempty(u->pids))
+ /* If the PID set is empty now, then let's finish this off
+ (On unified we use proper notifications) */
+ if (cg_unified() <= 0 && set_isempty(u->pids))
scope_notify_cgroup_empty_event(u);
}
@@ -473,6 +485,7 @@ int scope_abandon(Scope *s) {
if (!IN_SET(s->state, SCOPE_RUNNING, SCOPE_ABANDONED))
return -ESTALE;
+ s->was_abandoned = true;
s->controller = mfree(s->controller);
/* The client is no longer watching the remaining processes,
diff --git a/src/core/scope.h b/src/core/scope.h
index 2dc86325c5..eaf8e8b447 100644
--- a/src/core/scope.h
+++ b/src/core/scope.h
@@ -21,7 +21,9 @@
typedef struct Scope Scope;
+#include "cgroup.h"
#include "kill.h"
+#include "unit.h"
typedef enum ScopeResult {
SCOPE_SUCCESS,
@@ -43,6 +45,7 @@ struct Scope {
usec_t timeout_stop_usec;
char *controller;
+ bool was_abandoned;
sd_event_source *timer_event_source;
};
diff --git a/src/core/selinux-access.c b/src/core/selinux-access.c
index cc287d602d..2b96a9551b 100644
--- a/src/core/selinux-access.c
+++ b/src/core/selinux-access.c
@@ -191,7 +191,7 @@ int mac_selinux_generic_access_check(
const char *tclass = NULL, *scon = NULL;
struct audit_info audit_info = {};
_cleanup_free_ char *cl = NULL;
- security_context_t fcon = NULL;
+ char *fcon = NULL;
char **cmdline = NULL;
int r = 0;
diff --git a/src/core/selinux-setup.c b/src/core/selinux-setup.c
index 4072df58e6..527aa8add0 100644
--- a/src/core/selinux-setup.c
+++ b/src/core/selinux-setup.c
@@ -44,7 +44,7 @@ int mac_selinux_setup(bool *loaded_policy) {
#ifdef HAVE_SELINUX
int enforce = 0;
usec_t before_load, after_load;
- security_context_t con;
+ char *con;
int r;
union selinux_callback cb;
bool initialized = false;
diff --git a/src/core/service.c b/src/core/service.c
index 7ebabca5d6..eb125cb999 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -200,16 +200,27 @@ static void service_stop_watchdog(Service *s) {
s->watchdog_timestamp = DUAL_TIMESTAMP_NULL;
}
+static usec_t service_get_watchdog_usec(Service *s) {
+ assert(s);
+
+ if (s->watchdog_override_enable)
+ return s->watchdog_override_usec;
+ else
+ return s->watchdog_usec;
+}
+
static void service_start_watchdog(Service *s) {
int r;
+ usec_t watchdog_usec;
assert(s);
- if (s->watchdog_usec <= 0)
+ watchdog_usec = service_get_watchdog_usec(s);
+ if (watchdog_usec == 0 || watchdog_usec == USEC_INFINITY)
return;
if (s->watchdog_event_source) {
- r = sd_event_source_set_time(s->watchdog_event_source, usec_add(s->watchdog_timestamp.monotonic, s->watchdog_usec));
+ r = sd_event_source_set_time(s->watchdog_event_source, usec_add(s->watchdog_timestamp.monotonic, watchdog_usec));
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to reset watchdog timer: %m");
return;
@@ -221,7 +232,7 @@ static void service_start_watchdog(Service *s) {
UNIT(s)->manager->event,
&s->watchdog_event_source,
CLOCK_MONOTONIC,
- usec_add(s->watchdog_timestamp.monotonic, s->watchdog_usec), 0,
+ usec_add(s->watchdog_timestamp.monotonic, watchdog_usec), 0,
service_dispatch_watchdog, s);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to add watchdog timer: %m");
@@ -246,6 +257,17 @@ static void service_reset_watchdog(Service *s) {
service_start_watchdog(s);
}
+static void service_reset_watchdog_timeout(Service *s, usec_t watchdog_override_usec) {
+ assert(s);
+
+ s->watchdog_override_enable = true;
+ s->watchdog_override_usec = watchdog_override_usec;
+ service_reset_watchdog(s);
+
+ log_unit_debug(UNIT(s), "watchdog_usec="USEC_FMT, s->watchdog_usec);
+ log_unit_debug(UNIT(s), "watchdog_override_usec="USEC_FMT, s->watchdog_override_usec);
+}
+
static void service_fd_store_unlink(ServiceFDStore *fs) {
if (!fs)
@@ -300,6 +322,8 @@ static void service_done(Unit *u) {
s->control_command = NULL;
s->main_command = NULL;
+ dynamic_creds_unref(&s->dynamic_creds);
+
exit_status_set_free(&s->restart_prevent_status);
exit_status_set_free(&s->restart_force_status);
exit_status_set_free(&s->success_status);
@@ -318,6 +342,7 @@ static void service_done(Unit *u) {
s->bus_name_owner = mfree(s->bus_name_owner);
service_close_socket_fd(s);
+ s->peer = socket_peer_unref(s->peer);
unit_ref_unset(&s->accept_socket);
@@ -574,20 +599,9 @@ static int service_setup_bus_name(Service *s) {
if (!s->bus_name)
return 0;
- if (is_kdbus_available()) {
- const char *n;
-
- n = strjoina(s->bus_name, ".busname");
- r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, n, NULL, true);
- if (r < 0)
- return log_unit_error_errno(UNIT(s), r, "Failed to add dependency to .busname unit: %m");
-
- } else {
- /* If kdbus is not available, we know the dbus socket is required, hence pull it in, and require it */
- r = unit_add_dependency_by_name(UNIT(s), UNIT_REQUIRES, SPECIAL_DBUS_SOCKET, NULL, true);
- if (r < 0)
- return log_unit_error_errno(UNIT(s), r, "Failed to add dependency on " SPECIAL_DBUS_SOCKET ": %m");
- }
+ r = unit_add_dependency_by_name(UNIT(s), UNIT_REQUIRES, SPECIAL_DBUS_SOCKET, NULL, true);
+ if (r < 0)
+ return log_unit_error_errno(UNIT(s), r, "Failed to add dependency on " SPECIAL_DBUS_SOCKET ": %m");
/* Regardless if kdbus is used or not, we always want to be ordered against dbus.socket if both are in the transaction. */
r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_DBUS_SOCKET, NULL, true);
@@ -1019,6 +1033,9 @@ static int service_coldplug(Unit *u) {
if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD))
service_start_watchdog(s);
+ if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART))
+ (void) unit_setup_dynamic_creds(u);
+
service_set_state(s, s->deserialized_state);
return 0;
}
@@ -1173,6 +1190,10 @@ static int service_spawn(
if (r < 0)
return r;
+ r = unit_setup_dynamic_creds(UNIT(s));
+ if (r < 0)
+ return r;
+
if (pass_fds ||
s->exec_context.std_input == EXEC_INPUT_SOCKET ||
s->exec_context.std_output == EXEC_OUTPUT_SOCKET ||
@@ -1274,6 +1295,7 @@ static int service_spawn(
&s->exec_context,
&exec_params,
s->exec_runtime,
+ &s->dynamic_creds,
&pid);
if (r < 0)
return r;
@@ -1407,9 +1429,12 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
exec_runtime_destroy(s->exec_runtime);
s->exec_runtime = exec_runtime_unref(s->exec_runtime);
- /* Also, remove the runtime directory in */
+ /* Also, remove the runtime directory */
exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
+ /* Release the user, and destroy it if we are the only remaining owner */
+ dynamic_creds_destroy(&s->dynamic_creds);
+
/* Try to delete the pid file. At this point it will be
* out-of-date, and some software might be confused by it, so
* let's remove it. */
@@ -1663,7 +1688,7 @@ static void service_kill_control_processes(Service *s) {
return;
p = strjoina(UNIT(s)->cgroup_path, "/control");
- cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, p, SIGKILL, true, true, true, NULL);
+ cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, p, SIGKILL, CGROUP_SIGCONT|CGROUP_IGNORE_SELF|CGROUP_REMOVE, NULL, NULL, NULL);
}
static void service_enter_start(Service *s) {
@@ -2003,6 +2028,9 @@ static int service_start(Unit *u) {
s->notify_state = NOTIFY_UNKNOWN;
+ s->watchdog_override_enable = false;
+ s->watchdog_override_usec = 0;
+
service_enter_start_pre(s);
return 1;
}
@@ -2134,6 +2162,9 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
unit_serialize_item(u, f, "forbid-restart", yes_no(s->forbid_restart));
+ if (s->watchdog_override_enable)
+ unit_serialize_item_format(u, f, "watchdog-override-usec", USEC_FMT, s->watchdog_override_usec);
+
return 0;
}
@@ -2328,6 +2359,14 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
s->stderr_fd = fdset_remove(fds, fd);
s->exec_context.stdio_as_fds = true;
}
+ } else if (streq(key, "watchdog-override-usec")) {
+ usec_t watchdog_override_usec;
+ if (timestamp_deserialize(value, &watchdog_override_usec) < 0)
+ log_unit_debug(u, "Failed to parse watchdog_override_usec value: %s", value);
+ else {
+ s->watchdog_override_enable = true;
+ s->watchdog_override_usec = watchdog_override_usec;
+ }
} else
log_unit_debug(u, "Unknown serialization key: %s", key);
@@ -2800,8 +2839,9 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
unit_tidy_watch_pids(u, s->main_pid, s->control_pid);
unit_watch_all_pids(u);
- /* If the PID set is empty now, then let's finish this off */
- if (set_isempty(u->pids))
+ /* If the PID set is empty now, then let's finish this off
+ (On unified we use proper notifications) */
+ if (cg_unified() <= 0 && set_isempty(u->pids))
service_notify_cgroup_empty_event(u);
}
@@ -2905,12 +2945,15 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata) {
Service *s = SERVICE(userdata);
char t[FORMAT_TIMESPAN_MAX];
+ usec_t watchdog_usec;
assert(s);
assert(source == s->watchdog_event_source);
+ watchdog_usec = service_get_watchdog_usec(s);
+
log_unit_error(UNIT(s), "Watchdog timeout (limit %s)!",
- format_timespan(t, sizeof(t), s->watchdog_usec, 1));
+ format_timespan(t, sizeof(t), watchdog_usec, 1));
service_enter_signal(s, SERVICE_STOP_SIGABRT, SERVICE_FAILURE_WATCHDOG);
@@ -3047,6 +3090,15 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds)
service_add_fd_store_set(s, fds, name);
}
+ e = strv_find_startswith(tags, "WATCHDOG_USEC=");
+ if (e) {
+ usec_t watchdog_override_usec;
+ if (safe_atou64(e, &watchdog_override_usec) < 0)
+ log_unit_warning(u, "Failed to parse WATCHDOG_USEC=%s", e);
+ else
+ service_reset_watchdog_timeout(s, watchdog_override_usec);
+ }
+
/* Notify clients about changed status or main pid */
if (notify_dbus)
unit_add_to_dbus_queue(u);
@@ -3285,6 +3337,7 @@ const UnitVTable service_vtable = {
.cgroup_context_offset = offsetof(Service, cgroup_context),
.kill_context_offset = offsetof(Service, kill_context),
.exec_runtime_offset = offsetof(Service, exec_runtime),
+ .dynamic_creds_offset = offsetof(Service, dynamic_creds),
.sections =
"Unit\0"
diff --git a/src/core/service.h b/src/core/service.h
index 4af3d40439..888007cc0b 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -120,6 +120,8 @@ struct Service {
dual_timestamp watchdog_timestamp;
usec_t watchdog_usec;
+ usec_t watchdog_override_usec;
+ bool watchdog_override_enable;
sd_event_source *watchdog_event_source;
ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX];
@@ -146,9 +148,11 @@ struct Service {
/* Runtime data of the execution context */
ExecRuntime *exec_runtime;
+ DynamicCreds dynamic_creds;
pid_t main_pid, control_pid;
int socket_fd;
+ SocketPeer *peer;
bool socket_fd_selinux_context_net;
bool permissions_start_only;
diff --git a/src/core/shutdown.c b/src/core/shutdown.c
index e14755d84e..a795d875bb 100644
--- a/src/core/shutdown.c
+++ b/src/core/shutdown.c
@@ -157,7 +157,6 @@ static int switch_root_initramfs(void) {
return switch_root("/run/initramfs", "/oldroot", false, MS_BIND);
}
-
int main(int argc, char *argv[]) {
bool need_umount, need_swapoff, need_loop_detach, need_dm_detach;
bool in_container, use_watchdog = false;
@@ -203,20 +202,25 @@ int main(int argc, char *argv[]) {
}
(void) cg_get_root_path(&cgroup);
+ in_container = detect_container() > 0;
use_watchdog = !!getenv("WATCHDOG_USEC");
- /* lock us into memory */
+ /* Lock us into memory */
mlockall(MCL_CURRENT|MCL_FUTURE);
+ /* Synchronize everything that is not written to disk yet at this point already. This is a good idea so that
+ * slow IO is processed here already and the final process killing spree is not impacted by processes
+ * desperately trying to sync IO to disk within their timeout. */
+ if (!in_container)
+ sync();
+
log_info("Sending SIGTERM to remaining processes...");
broadcast_signal(SIGTERM, true, true);
log_info("Sending SIGKILL to remaining processes...");
broadcast_signal(SIGKILL, true, false);
- in_container = detect_container() > 0;
-
need_umount = !in_container;
need_swapoff = !in_container;
need_loop_detach = !in_container;
@@ -345,10 +349,10 @@ int main(int argc, char *argv[]) {
need_loop_detach ? " loop devices," : "",
need_dm_detach ? " DM devices," : "");
- /* The kernel will automaticall flush ATA disks and suchlike
- * on reboot(), but the file systems need to be synce'd
- * explicitly in advance. So let's do this here, but not
- * needlessly slow down containers. */
+ /* The kernel will automatically flush ATA disks and suchlike on reboot(), but the file systems need to be
+ * sync'ed explicitly in advance. So let's do this here, but not needlessly slow down containers. Note that we
+ * sync'ed things already once above, but we did some more work since then which might have caused IO, hence
+ * let's doit once more. */
if (!in_container)
sync();
diff --git a/src/core/socket.c b/src/core/socket.c
index f6204d04bf..ff55885fb3 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -57,6 +57,7 @@
#include "unit-printf.h"
#include "unit.h"
#include "user-util.h"
+#include "in-addr-util.h"
static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = {
[SOCKET_DEAD] = UNIT_INACTIVE,
@@ -77,6 +78,9 @@ static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = {
static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata);
static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata);
+SocketPeer *socket_peer_new(void);
+int socket_find_peer(Socket *s, int fd, SocketPeer **p);
+
static void socket_init(Unit *u) {
Socket *s = SOCKET(u);
@@ -141,15 +145,23 @@ void socket_free_ports(Socket *s) {
static void socket_done(Unit *u) {
Socket *s = SOCKET(u);
+ SocketPeer *p;
assert(s);
socket_free_ports(s);
+ while ((p = hashmap_steal_first(s->peers_by_address)))
+ p->socket = NULL;
+
+ s->peers_by_address = hashmap_free(s->peers_by_address);
+
s->exec_runtime = exec_runtime_unref(s->exec_runtime);
exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX);
s->control_command = NULL;
+ dynamic_creds_unref(&s->dynamic_creds);
+
socket_unwatch_control_pid(s);
unit_ref_unset(&s->service);
@@ -466,6 +478,40 @@ static int socket_verify(Socket *s) {
return 0;
}
+static void peer_address_hash_func(const void *p, struct siphash *state) {
+ const SocketPeer *s = p;
+
+ assert(s);
+
+ if (s->peer.sa.sa_family == AF_INET)
+ siphash24_compress(&s->peer.in.sin_addr, sizeof(s->peer.in.sin_addr), state);
+ else if (s->peer.sa.sa_family == AF_INET6)
+ siphash24_compress(&s->peer.in6.sin6_addr, sizeof(s->peer.in6.sin6_addr), state);
+}
+
+static int peer_address_compare_func(const void *a, const void *b) {
+ const SocketPeer *x = a, *y = b;
+
+ if (x->peer.sa.sa_family < y->peer.sa.sa_family)
+ return -1;
+ if (x->peer.sa.sa_family > y->peer.sa.sa_family)
+ return 1;
+
+ switch(x->peer.sa.sa_family) {
+ case AF_INET:
+ return memcmp(&x->peer.in.sin_addr, &y->peer.in.sin_addr, sizeof(x->peer.in.sin_addr));
+ case AF_INET6:
+ return memcmp(&x->peer.in6.sin6_addr, &y->peer.in6.sin6_addr, sizeof(x->peer.in6.sin6_addr));
+ }
+
+ return -1;
+}
+
+const struct hash_ops peer_address_hash_ops = {
+ .hash = peer_address_hash_func,
+ .compare = peer_address_compare_func
+};
+
static int socket_load(Unit *u) {
Socket *s = SOCKET(u);
int r;
@@ -473,6 +519,10 @@ static int socket_load(Unit *u) {
assert(u);
assert(u->load_state == UNIT_STUB);
+ r = hashmap_ensure_allocated(&s->peers_by_address, &peer_address_hash_ops);
+ if (r < 0)
+ return r;
+
r = unit_load_fragment_and_dropin(u);
if (r < 0)
return r;
@@ -730,16 +780,16 @@ static int instance_from_socket(int fd, unsigned nr, char **instance) {
case AF_INET: {
uint32_t
- a = ntohl(local.in.sin_addr.s_addr),
- b = ntohl(remote.in.sin_addr.s_addr);
+ a = be32toh(local.in.sin_addr.s_addr),
+ b = be32toh(remote.in.sin_addr.s_addr);
if (asprintf(&r,
"%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u",
nr,
a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF,
- ntohs(local.in.sin_port),
+ be16toh(local.in.sin_port),
b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF,
- ntohs(remote.in.sin_port)) < 0)
+ be16toh(remote.in.sin_port)) < 0)
return -ENOMEM;
break;
@@ -760,9 +810,9 @@ static int instance_from_socket(int fd, unsigned nr, char **instance) {
"%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u",
nr,
a[0], a[1], a[2], a[3],
- ntohs(local.in6.sin6_port),
+ be16toh(local.in6.sin6_port),
b[0], b[1], b[2], b[3],
- ntohs(remote.in6.sin6_port)) < 0)
+ be16toh(remote.in6.sin6_port)) < 0)
return -ENOMEM;
} else {
char a[INET6_ADDRSTRLEN], b[INET6_ADDRSTRLEN];
@@ -771,9 +821,9 @@ static int instance_from_socket(int fd, unsigned nr, char **instance) {
"%u-%s:%u-%s:%u",
nr,
inet_ntop(AF_INET6, &local.in6.sin6_addr, a, sizeof(a)),
- ntohs(local.in6.sin6_port),
+ be16toh(local.in6.sin6_port),
inet_ntop(AF_INET6, &remote.in6.sin6_addr, b, sizeof(b)),
- ntohs(remote.in6.sin6_port)) < 0)
+ be16toh(remote.in6.sin6_port)) < 0)
return -ENOMEM;
}
@@ -1602,6 +1652,9 @@ static int socket_coldplug(Unit *u) {
return r;
}
+ if (!IN_SET(s->deserialized_state, SOCKET_DEAD, SOCKET_FAILED))
+ (void) unit_setup_dynamic_creds(u);
+
socket_set_state(s, s->deserialized_state);
return 0;
}
@@ -1633,6 +1686,10 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
if (r < 0)
return r;
+ r = unit_setup_dynamic_creds(UNIT(s));
+ if (r < 0)
+ return r;
+
r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec));
if (r < 0)
return r;
@@ -1654,6 +1711,7 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
&s->exec_context,
&exec_params,
s->exec_runtime,
+ &s->dynamic_creds,
&pid);
if (r < 0)
return r;
@@ -1757,12 +1815,14 @@ static void socket_enter_dead(Socket *s, SocketResult f) {
if (f != SOCKET_SUCCESS)
s->result = f;
+ socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD);
+
exec_runtime_destroy(s->exec_runtime);
s->exec_runtime = exec_runtime_unref(s->exec_runtime);
exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
- socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD);
+ dynamic_creds_destroy(&s->dynamic_creds);
}
static void socket_enter_signal(Socket *s, SocketState state, SocketResult f);
@@ -2038,6 +2098,7 @@ static void socket_enter_running(Socket *s, int cfd) {
socket_set_state(s, SOCKET_RUNNING);
} else {
_cleanup_free_ char *prefix = NULL, *instance = NULL, *name = NULL;
+ _cleanup_(socket_peer_unrefp) SocketPeer *p = NULL;
Service *service;
if (s->n_connections >= s->max_connections) {
@@ -2046,6 +2107,21 @@ static void socket_enter_running(Socket *s, int cfd) {
return;
}
+ if (s->max_connections_per_source > 0) {
+ r = socket_find_peer(s, cfd, &p);
+ if (r < 0) {
+ safe_close(cfd);
+ return;
+ }
+
+ if (p->n_ref > s->max_connections_per_source) {
+ log_unit_warning(UNIT(s), "Too many incoming connections (%u) from source, refusing connection attempt.", p->n_ref);
+ safe_close(cfd);
+ p = NULL;
+ return;
+ }
+ }
+
r = socket_instantiate_service(s);
if (r < 0)
goto fail;
@@ -2087,6 +2163,11 @@ static void socket_enter_running(Socket *s, int cfd) {
cfd = -1; /* We passed ownership of the fd to the service now. Forget it here. */
s->n_connections++;
+ if (s->max_connections_per_source > 0) {
+ service->peer = socket_peer_ref(p);
+ p = NULL;
+ }
+
r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL);
if (r < 0) {
/* We failed to activate the new service, but it still exists. Let's make sure the service
@@ -2232,7 +2313,9 @@ static int socket_stop(Unit *u) {
static int socket_serialize(Unit *u, FILE *f, FDSet *fds) {
Socket *s = SOCKET(u);
+ SocketPeer *k;
SocketPort *p;
+ Iterator i;
int r;
assert(u);
@@ -2283,6 +2366,16 @@ static int socket_serialize(Unit *u, FILE *f, FDSet *fds) {
}
}
+ HASHMAP_FOREACH(k, s->peers_by_address, i) {
+ _cleanup_free_ char *t = NULL;
+
+ r = sockaddr_pretty(&k->peer.sa, FAMILY_ADDRESS_SIZE(k->peer.sa.sa_family), true, true, &t);
+ if (r < 0)
+ return r;
+
+ unit_serialize_item_format(u, f, "peer", "%u %s", k->n_ref, t);
+ }
+
return 0;
}
@@ -2446,6 +2539,33 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value,
}
}
+ } else if (streq(key, "peer")) {
+ _cleanup_(socket_peer_unrefp) SocketPeer *p;
+ int n_ref, skip = 0;
+ SocketAddress a;
+ int r;
+
+ if (sscanf(value, "%u %n", &n_ref, &skip) < 1 || n_ref < 1)
+ log_unit_debug(u, "Failed to parse socket peer value: %s", value);
+ else {
+ r = socket_address_parse(&a, value+skip);
+ if (r < 0)
+ return r;
+
+ p = socket_peer_new();
+ if (!p)
+ return log_oom();
+
+ p->n_ref = n_ref;
+ memcpy(&p->peer, &a.sockaddr, sizeof(a.sockaddr));
+ p->socket = s;
+
+ r = hashmap_put(s->peers_by_address, p, p);
+ if (r < 0)
+ return r;
+
+ p = NULL;
+ }
} else
log_unit_debug(UNIT(s), "Unknown serialization key: %s", key);
@@ -2542,6 +2662,83 @@ _pure_ static bool socket_check_gc(Unit *u) {
return s->n_connections > 0;
}
+SocketPeer *socket_peer_new(void) {
+ SocketPeer *p;
+
+ p = new0(SocketPeer, 1);
+ if (!p)
+ return NULL;
+
+ p->n_ref = 1;
+
+ return p;
+}
+
+SocketPeer *socket_peer_ref(SocketPeer *p) {
+ if (!p)
+ return NULL;
+
+ assert(p->n_ref > 0);
+ p->n_ref++;
+
+ return p;
+}
+
+SocketPeer *socket_peer_unref(SocketPeer *p) {
+ if (!p)
+ return NULL;
+
+ assert(p->n_ref > 0);
+
+ p->n_ref--;
+
+ if (p->n_ref > 0)
+ return NULL;
+
+ if (p->socket)
+ (void) hashmap_remove(p->socket->peers_by_address, p);
+
+ free(p);
+
+ return NULL;
+}
+
+int socket_find_peer(Socket *s, int fd, SocketPeer **p) {
+ _cleanup_free_ SocketPeer *remote = NULL;
+ SocketPeer sa, *i;
+ socklen_t salen = sizeof(sa.peer);
+ int r;
+
+ assert(fd >= 0);
+ assert(s);
+
+ r = getpeername(fd, &sa.peer.sa, &salen);
+ if (r < 0)
+ return log_error_errno(errno, "getpeername failed: %m");
+
+ i = hashmap_get(s->peers_by_address, &sa);
+ if (i) {
+ *p = i;
+ return 1;
+ }
+
+ remote = socket_peer_new();
+ if (!remote)
+ return log_oom();
+
+ memcpy(&remote->peer, &sa.peer, sizeof(union sockaddr_union));
+ remote->socket = s;
+
+ r = hashmap_put(s->peers_by_address, remote, remote);
+ if (r < 0)
+ return r;
+
+ *p = remote;
+ remote = NULL;
+
+ return 0;
+}
+
static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
SocketPort *p = userdata;
int cfd = -1;
@@ -2930,6 +3127,7 @@ const UnitVTable socket_vtable = {
.cgroup_context_offset = offsetof(Socket, cgroup_context),
.kill_context_offset = offsetof(Socket, kill_context),
.exec_runtime_offset = offsetof(Socket, exec_runtime),
+ .dynamic_creds_offset = offsetof(Socket, dynamic_creds),
.sections =
"Unit\0"
diff --git a/src/core/socket.h b/src/core/socket.h
index 0f1ac69c6f..2fe38ef2aa 100644
--- a/src/core/socket.h
+++ b/src/core/socket.h
@@ -20,6 +20,7 @@
***/
typedef struct Socket Socket;
+typedef struct SocketPeer SocketPeer;
#include "mount.h"
#include "service.h"
@@ -79,9 +80,12 @@ struct Socket {
LIST_HEAD(SocketPort, ports);
+ Hashmap *peers_by_address;
+
unsigned n_accepted;
unsigned n_connections;
unsigned max_connections;
+ unsigned max_connections_per_source;
unsigned backlog;
unsigned keep_alive_cnt;
@@ -94,7 +98,9 @@ struct Socket {
ExecContext exec_context;
KillContext kill_context;
CGroupContext cgroup_context;
+
ExecRuntime *exec_runtime;
+ DynamicCreds dynamic_creds;
/* For Accept=no sockets refers to the one service we'll
activate. For Accept=yes sockets is either NULL, or filled
@@ -162,6 +168,18 @@ struct Socket {
RateLimit trigger_limit;
};
+struct SocketPeer {
+ unsigned n_ref;
+
+ Socket *socket;
+ union sockaddr_union peer;
+};
+
+SocketPeer *socket_peer_ref(SocketPeer *p);
+SocketPeer *socket_peer_unref(SocketPeer *p);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(SocketPeer*, socket_peer_unref);
+
/* Called from the service code when collecting fds */
int socket_collect_fds(Socket *s, int **fds);
diff --git a/src/core/swap.c b/src/core/swap.c
index a532b15be8..66a318d01f 100644
--- a/src/core/swap.c
+++ b/src/core/swap.c
@@ -153,6 +153,8 @@ static void swap_done(Unit *u) {
exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX);
s->control_command = NULL;
+ dynamic_creds_unref(&s->dynamic_creds);
+
swap_unwatch_control_pid(s);
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
@@ -553,6 +555,9 @@ static int swap_coldplug(Unit *u) {
return r;
}
+ if (!IN_SET(new_state, SWAP_DEAD, SWAP_FAILED))
+ (void) unit_setup_dynamic_creds(u);
+
swap_set_state(s, new_state);
return 0;
}
@@ -628,6 +633,10 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) {
if (r < 0)
goto fail;
+ r = unit_setup_dynamic_creds(UNIT(s));
+ if (r < 0)
+ return r;
+
r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec));
if (r < 0)
goto fail;
@@ -644,6 +653,7 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) {
&s->exec_context,
&exec_params,
s->exec_runtime,
+ &s->dynamic_creds,
&pid);
if (r < 0)
goto fail;
@@ -668,12 +678,14 @@ static void swap_enter_dead(Swap *s, SwapResult f) {
if (f != SWAP_SUCCESS)
s->result = f;
+ swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD);
+
exec_runtime_destroy(s->exec_runtime);
s->exec_runtime = exec_runtime_unref(s->exec_runtime);
exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager));
- swap_set_state(s, s->result != SWAP_SUCCESS ? SWAP_FAILED : SWAP_DEAD);
+ dynamic_creds_destroy(&s->dynamic_creds);
}
static void swap_enter_active(Swap *s, SwapResult f) {
@@ -1466,6 +1478,7 @@ const UnitVTable swap_vtable = {
.cgroup_context_offset = offsetof(Swap, cgroup_context),
.kill_context_offset = offsetof(Swap, kill_context),
.exec_runtime_offset = offsetof(Swap, exec_runtime),
+ .dynamic_creds_offset = offsetof(Swap, dynamic_creds),
.sections =
"Unit\0"
diff --git a/src/core/swap.h b/src/core/swap.h
index fbf66debdc..b0ef50f1e8 100644
--- a/src/core/swap.h
+++ b/src/core/swap.h
@@ -82,6 +82,7 @@ struct Swap {
CGroupContext cgroup_context;
ExecRuntime *exec_runtime;
+ DynamicCreds dynamic_creds;
SwapState state, deserialized_state;
diff --git a/src/core/system.conf b/src/core/system.conf
index db8b7acd78..c6bb050aac 100644
--- a/src/core/system.conf
+++ b/src/core/system.conf
@@ -42,7 +42,7 @@
#DefaultBlockIOAccounting=no
#DefaultMemoryAccounting=no
#DefaultTasksAccounting=yes
-#DefaultTasksMax=512
+#DefaultTasksMax=15%
#DefaultLimitCPU=
#DefaultLimitFSIZE=
#DefaultLimitDATA=
diff --git a/src/core/transaction.c b/src/core/transaction.c
index e06a48a2f1..8370b864fb 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -373,7 +373,7 @@ static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsi
delete = NULL;
for (k = from; k; k = ((k->generation == generation && k->marker != k) ? k->marker : NULL)) {
- /* logging for j not k here here to provide consistent narrative */
+ /* logging for j not k here to provide consistent narrative */
log_unit_warning(j->unit,
"Found dependency on %s/%s",
k->unit->id, job_type_to_string(k->type));
@@ -392,7 +392,7 @@ static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsi
if (delete) {
const char *status;
- /* logging for j not k here here to provide consistent narrative */
+ /* logging for j not k here to provide consistent narrative */
log_unit_warning(j->unit,
"Breaking ordering cycle by deleting job %s/%s",
delete->unit->id, job_type_to_string(delete->type));
@@ -591,6 +591,9 @@ static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) {
HASHMAP_FOREACH(j, m->jobs, i) {
assert(j->installed);
+ if (j->unit->ignore_on_isolate)
+ continue;
+
if (hashmap_get(tr->jobs, j->unit))
continue;
diff --git a/src/core/unit.c b/src/core/unit.c
index e98086a3f6..ff7c562fba 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -100,6 +100,7 @@ Unit *unit_new(Manager *m, size_t size) {
u->on_failure_job_mode = JOB_REPLACE;
u->cgroup_inotify_wd = -1;
u->job_timeout = USEC_INFINITY;
+ u->sigchldgen = 0;
RATELIMIT_INIT(u->start_limit, m->default_start_limit_interval, m->default_start_limit_burst);
RATELIMIT_INIT(u->auto_stop_ratelimit, 10 * USEC_PER_SEC, 16);
@@ -1682,7 +1683,7 @@ static void unit_check_unneeded(Unit *u) {
if (unit_active_or_pending(other))
return;
- /* If stopping a unit fails continously we might enter a stop
+ /* If stopping a unit fails continuously we might enter a stop
* loop here, hence stop acting on the service being
* unnecessary after a while. */
if (!ratelimit_test(&u->auto_stop_ratelimit)) {
@@ -1727,7 +1728,7 @@ static void unit_check_binds_to(Unit *u) {
if (!stop)
return;
- /* If stopping a unit fails continously we might enter a stop
+ /* If stopping a unit fails continuously we might enter a stop
* loop here, hence stop acting on the service being
* unnecessary after a while. */
if (!ratelimit_test(&u->auto_stop_ratelimit)) {
@@ -3143,7 +3144,7 @@ int unit_kill_common(
if (!pid_set)
return -ENOMEM;
- q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, false, false, pid_set);
+ q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, 0, pid_set, NULL, NULL);
if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT)
r = q;
else
@@ -3223,6 +3224,33 @@ void unit_ref_unset(UnitRef *ref) {
ref->unit = NULL;
}
+static int user_from_unit_name(Unit *u, char **ret) {
+
+ static const uint8_t hash_key[] = {
+ 0x58, 0x1a, 0xaf, 0xe6, 0x28, 0x58, 0x4e, 0x96,
+ 0xb4, 0x4e, 0xf5, 0x3b, 0x8c, 0x92, 0x07, 0xec
+ };
+
+ _cleanup_free_ char *n = NULL;
+ int r;
+
+ r = unit_name_to_prefix(u->id, &n);
+ if (r < 0)
+ return r;
+
+ if (valid_user_group_name(n)) {
+ *ret = n;
+ n = NULL;
+ return 0;
+ }
+
+ /* If we can't use the unit name as a user name, then let's hash it and use that */
+ if (asprintf(ret, "_du%016" PRIx64, siphash24(n, strlen(n), hash_key)) < 0)
+ return -ENOMEM;
+
+ return 0;
+}
+
int unit_patch_contexts(Unit *u) {
CGroupContext *cc;
ExecContext *ec;
@@ -3267,6 +3295,22 @@ int unit_patch_contexts(Unit *u) {
if (ec->private_devices)
ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD);
+
+ if (ec->dynamic_user) {
+ if (!ec->user) {
+ r = user_from_unit_name(u, &ec->user);
+ if (r < 0)
+ return r;
+ }
+
+ if (!ec->group) {
+ ec->group = strdup(ec->user);
+ if (!ec->group)
+ return -ENOMEM;
+ }
+
+ ec->private_tmp = true;
+ }
}
cc = unit_get_cgroup_context(u);
@@ -3364,6 +3408,7 @@ int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, co
/* When this is a transient unit file in creation, then let's not create a new drop-in but instead
* write to the transient unit file. */
fputs(data, u->transient_file);
+ fputc('\n', u->transient_file);
return 0;
}
@@ -3375,7 +3420,7 @@ int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, co
return -EINVAL;
wrapped = strjoina("# This is a drop-in unit file extension, created via \"systemctl set-property\"\n"
- "or an equivalent operation. Do not edit.\n",
+ "# or an equivalent operation. Do not edit.\n",
data,
"\n");
@@ -3503,7 +3548,6 @@ int unit_make_transient(Unit *u) {
unit_add_to_dbus_queue(u);
unit_add_to_gc_queue(u);
- unit_add_to_load_queue(u);
fputs("# This is a transient unit file, created programmatically via the systemd API. Do not edit.\n",
u->transient_file);
@@ -3511,6 +3555,43 @@ int unit_make_transient(Unit *u) {
return 0;
}
+static void log_kill(pid_t pid, int sig, void *userdata) {
+ _cleanup_free_ char *comm = NULL;
+
+ (void) get_process_comm(pid, &comm);
+
+ /* Don't log about processes marked with brackets, under the assumption that these are temporary processes
+ only, like for example systemd's own PAM stub process. */
+ if (comm && comm[0] == '(')
+ return;
+
+ log_unit_notice(userdata,
+ "Killing process " PID_FMT " (%s) with signal SIG%s.",
+ pid,
+ strna(comm),
+ signal_to_string(sig));
+}
+
+static int operation_to_signal(KillContext *c, KillOperation k) {
+ assert(c);
+
+ switch (k) {
+
+ case KILL_TERMINATE:
+ case KILL_TERMINATE_AND_LOG:
+ return c->kill_signal;
+
+ case KILL_KILL:
+ return SIGKILL;
+
+ case KILL_ABORT:
+ return SIGABRT;
+
+ default:
+ assert_not_reached("KillOperation unknown");
+ }
+}
+
int unit_kill_context(
Unit *u,
KillContext *c,
@@ -3519,58 +3600,63 @@ int unit_kill_context(
pid_t control_pid,
bool main_pid_alien) {
- bool wait_for_exit = false;
+ bool wait_for_exit = false, send_sighup;
+ cg_kill_log_func_t log_func;
int sig, r;
assert(u);
assert(c);
+ /* Kill the processes belonging to this unit, in preparation for shutting the unit down. Returns > 0 if we
+ * killed something worth waiting for, 0 otherwise. */
+
if (c->kill_mode == KILL_NONE)
return 0;
- switch (k) {
- case KILL_KILL:
- sig = SIGKILL;
- break;
- case KILL_ABORT:
- sig = SIGABRT;
- break;
- case KILL_TERMINATE:
- sig = c->kill_signal;
- break;
- default:
- assert_not_reached("KillOperation unknown");
- }
+ sig = operation_to_signal(c, k);
+
+ send_sighup =
+ c->send_sighup &&
+ IN_SET(k, KILL_TERMINATE, KILL_TERMINATE_AND_LOG) &&
+ sig != SIGHUP;
+
+ log_func =
+ k != KILL_TERMINATE ||
+ IN_SET(sig, SIGKILL, SIGABRT) ? log_kill : NULL;
if (main_pid > 0) {
- r = kill_and_sigcont(main_pid, sig);
+ if (log_func)
+ log_func(main_pid, sig, u);
+ r = kill_and_sigcont(main_pid, sig);
if (r < 0 && r != -ESRCH) {
_cleanup_free_ char *comm = NULL;
- get_process_comm(main_pid, &comm);
+ (void) get_process_comm(main_pid, &comm);
log_unit_warning_errno(u, r, "Failed to kill main process " PID_FMT " (%s), ignoring: %m", main_pid, strna(comm));
} else {
if (!main_pid_alien)
wait_for_exit = true;
- if (c->send_sighup && k == KILL_TERMINATE)
+ if (r != -ESRCH && send_sighup)
(void) kill(main_pid, SIGHUP);
}
}
if (control_pid > 0) {
- r = kill_and_sigcont(control_pid, sig);
+ if (log_func)
+ log_func(control_pid, sig, u);
+ r = kill_and_sigcont(control_pid, sig);
if (r < 0 && r != -ESRCH) {
_cleanup_free_ char *comm = NULL;
- get_process_comm(control_pid, &comm);
+ (void) get_process_comm(control_pid, &comm);
log_unit_warning_errno(u, r, "Failed to kill control process " PID_FMT " (%s), ignoring: %m", control_pid, strna(comm));
} else {
wait_for_exit = true;
- if (c->send_sighup && k == KILL_TERMINATE)
+ if (r != -ESRCH && send_sighup)
(void) kill(control_pid, SIGHUP);
}
}
@@ -3584,7 +3670,11 @@ int unit_kill_context(
if (!pid_set)
return -ENOMEM;
- r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, sig, true, k != KILL_TERMINATE, false, pid_set);
+ r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path,
+ sig,
+ CGROUP_SIGCONT|CGROUP_IGNORE_SELF,
+ pid_set,
+ log_func, u);
if (r < 0) {
if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
log_unit_warning_errno(u, r, "Failed to kill control group %s, ignoring: %m", u->cgroup_path);
@@ -3609,14 +3699,18 @@ int unit_kill_context(
(detect_container() == 0 && !unit_cgroup_delegate(u)))
wait_for_exit = true;
- if (c->send_sighup && k != KILL_KILL) {
+ if (send_sighup) {
set_free(pid_set);
pid_set = unit_pid_set(main_pid, control_pid);
if (!pid_set)
return -ENOMEM;
- cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, SIGHUP, false, true, false, pid_set);
+ cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path,
+ SIGHUP,
+ CGROUP_IGNORE_SELF,
+ pid_set,
+ NULL, NULL);
}
}
}
@@ -3725,6 +3819,26 @@ int unit_setup_exec_runtime(Unit *u) {
return exec_runtime_make(rt, unit_get_exec_context(u), u->id);
}
+int unit_setup_dynamic_creds(Unit *u) {
+ ExecContext *ec;
+ DynamicCreds *dcreds;
+ size_t offset;
+
+ assert(u);
+
+ offset = UNIT_VTABLE(u)->dynamic_creds_offset;
+ assert(offset > 0);
+ dcreds = (DynamicCreds*) ((uint8_t*) u + offset);
+
+ ec = unit_get_exec_context(u);
+ assert(ec);
+
+ if (!ec->dynamic_user)
+ return 0;
+
+ return dynamic_creds_acquire(dcreds, u->manager, ec->user, ec->group);
+}
+
bool unit_type_supported(UnitType t) {
if (_unlikely_(t < 0))
return false;
@@ -3789,7 +3903,7 @@ bool unit_is_pristine(Unit *u) {
/* Check if the unit already exists or is already around,
* in a number of different ways. Note that to cater for unit
* types such as slice, we are generally fine with units that
- * are marked UNIT_LOADED even even though nothing was
+ * are marked UNIT_LOADED even though nothing was
* actually loaded, as those unit types don't require a file
* on disk to validly load. */
diff --git a/src/core/unit.h b/src/core/unit.h
index 08a927962d..47eb8d50a6 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -36,6 +36,7 @@ typedef struct UnitStatusMessageFormats UnitStatusMessageFormats;
typedef enum KillOperation {
KILL_TERMINATE,
+ KILL_TERMINATE_AND_LOG,
KILL_KILL,
KILL_ABORT,
_KILL_OPERATION_MAX,
@@ -162,6 +163,9 @@ struct Unit {
* process SIGCHLD for */
Set *pids;
+ /* Used in sigchld event invocation to avoid repeat events being invoked */
+ uint64_t sigchldgen;
+
/* Used during GC sweeps */
unsigned gc_marker;
@@ -287,6 +291,10 @@ struct UnitVTable {
* that */
size_t exec_runtime_offset;
+ /* If greater than 0, the offset into the object where the pointer to DynamicCreds is found, if the unit type
+ * has that. */
+ size_t dynamic_creds_offset;
+
/* The name of the configuration file section with the private settings of this unit */
const char *private_section;
@@ -585,6 +593,7 @@ CGroupContext *unit_get_cgroup_context(Unit *u) _pure_;
ExecRuntime *unit_get_exec_runtime(Unit *u) _pure_;
int unit_setup_exec_runtime(Unit *u);
+int unit_setup_dynamic_creds(Unit *u);
int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data);
int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_(4,5);
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
index 999de63900..e3d17c864d 100644
--- a/src/coredump/coredump.c
+++ b/src/coredump/coredump.c
@@ -157,10 +157,8 @@ static int fix_acl(int fd, uid_t uid) {
if (acl_create_entry(&acl, &entry) < 0 ||
acl_set_tag_type(entry, ACL_USER) < 0 ||
- acl_set_qualifier(entry, &uid) < 0) {
- log_error_errno(errno, "Failed to patch ACL: %m");
- return -errno;
- }
+ acl_set_qualifier(entry, &uid) < 0)
+ return log_error_errno(errno, "Failed to patch ACL: %m");
if (acl_get_permset(entry, &permset) < 0 ||
acl_add_perm(permset, ACL_READ) < 0)
@@ -560,6 +558,89 @@ static int compose_open_fds(pid_t pid, char **open_fds) {
return 0;
}
+static int get_process_ns(pid_t pid, const char *namespace, ino_t *ns) {
+ const char *p;
+ struct stat stbuf;
+ _cleanup_close_ int proc_ns_dir_fd;
+
+ p = procfs_file_alloca(pid, "ns");
+
+ proc_ns_dir_fd = open(p, O_DIRECTORY | O_CLOEXEC | O_RDONLY);
+ if (proc_ns_dir_fd < 0)
+ return -errno;
+
+ if (fstatat(proc_ns_dir_fd, namespace, &stbuf, /* flags */0) < 0)
+ return -errno;
+
+ *ns = stbuf.st_ino;
+ return 0;
+}
+
+static int get_mount_namespace_leader(pid_t pid, pid_t *container_pid) {
+ pid_t cpid = pid, ppid = 0;
+ ino_t proc_mntns;
+ int r = 0;
+
+ r = get_process_ns(pid, "mnt", &proc_mntns);
+ if (r < 0)
+ return r;
+
+ while (1) {
+ ino_t parent_mntns;
+
+ r = get_process_ppid(cpid, &ppid);
+ if (r < 0)
+ return r;
+
+ r = get_process_ns(ppid, "mnt", &parent_mntns);
+ if (r < 0)
+ return r;
+
+ if (proc_mntns != parent_mntns)
+ break;
+
+ if (ppid == 1)
+ return -ENOENT;
+
+ cpid = ppid;
+ }
+
+ *container_pid = ppid;
+ return 0;
+}
+
+/* Returns 1 if the parent was found.
+ * Returns 0 if there is not a process we can call the pid's
+ * container parent (the pid's process isn't 'containerized').
+ * Returns a negative number on errors.
+ */
+static int get_process_container_parent_cmdline(pid_t pid, char** cmdline) {
+ int r = 0;
+ pid_t container_pid;
+ const char *proc_root_path;
+ struct stat root_stat, proc_root_stat;
+
+ /* To compare inodes of / and /proc/[pid]/root */
+ if (stat("/", &root_stat) < 0)
+ return -errno;
+
+ proc_root_path = procfs_file_alloca(pid, "root");
+ if (stat(proc_root_path, &proc_root_stat) < 0)
+ return -errno;
+
+ /* The process uses system root. */
+ if (proc_root_stat.st_ino == root_stat.st_ino) {
+ *cmdline = NULL;
+ return 0;
+ }
+
+ r = get_mount_namespace_leader(pid, &container_pid);
+ if (r < 0)
+ return r;
+
+ return get_process_cmdline(container_pid, 0, false, cmdline);
+}
+
static int change_uid_gid(const char *context[]) {
uid_t uid;
gid_t gid;
@@ -756,7 +837,6 @@ static int process_socket(int fd) {
iovec[n_iovec].iov_len = l;
iovec[n_iovec].iov_base = malloc(l + 1);
-
if (!iovec[n_iovec].iov_base) {
r = log_oom();
goto finish;
@@ -811,7 +891,7 @@ static int process_socket(int fd) {
goto finish;
}
- /* Make sure we we got all data we really need */
+ /* Make sure we got all data we really need */
assert(context[CONTEXT_PID]);
assert(context[CONTEXT_UID]);
assert(context[CONTEXT_GID]);
@@ -852,12 +932,42 @@ static int send_iovec(const struct iovec iovec[], size_t n_iovec, int input_fd)
return log_error_errno(errno, "Failed to connect to coredump service: %m");
for (i = 0; i < n_iovec; i++) {
- ssize_t n;
- assert(iovec[i].iov_len > 0);
+ struct msghdr mh = {
+ .msg_iov = (struct iovec*) iovec + i,
+ .msg_iovlen = 1,
+ };
+ struct iovec copy[2];
+
+ for (;;) {
+ if (sendmsg(fd, &mh, MSG_NOSIGNAL) >= 0)
+ break;
+
+ if (errno == EMSGSIZE && mh.msg_iov[0].iov_len > 0) {
+ /* This field didn't fit? That's a pity. Given that this is just metadata,
+ * let's truncate the field at half, and try again. We append three dots, in
+ * order to show that this is truncated. */
+
+ if (mh.msg_iov != copy) {
+ /* We don't want to modify the caller's iovec, hence let's create our
+ * own array, consisting of two new iovecs, where the first is a
+ * (truncated) copy of what we want to send, and the second one
+ * contains the trailing dots. */
+ copy[0] = iovec[i];
+ copy[1] = (struct iovec) {
+ .iov_base = (char[]) { '.', '.', '.' },
+ .iov_len = 3,
+ };
+
+ mh.msg_iov = copy;
+ mh.msg_iovlen = 2;
+ }
+
+ copy[0].iov_len /= 2; /* halve it, and try again */
+ continue;
+ }
- n = send(fd, iovec[i].iov_base, iovec[i].iov_len, MSG_NOSIGNAL);
- if (n < 0)
return log_error_errno(errno, "Failed to send coredump datagram: %m");
+ }
}
r = send_one_fd(fd, input_fd, 0);
@@ -867,7 +977,7 @@ static int send_iovec(const struct iovec iovec[], size_t n_iovec, int input_fd)
return 0;
}
-static int process_journald_crash(const char *context[], int input_fd) {
+static int process_special_crash(const char *context[], int input_fd) {
_cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1;
_cleanup_free_ char *filename = NULL;
uint64_t coredump_size;
@@ -876,7 +986,7 @@ static int process_journald_crash(const char *context[], int input_fd) {
assert(context);
assert(input_fd >= 0);
- /* If we are journald, we cut things short, don't write to the journal, but still create a coredump. */
+ /* If we are pid1 or journald, we cut things short, don't write to the journal, but still create a coredump. */
if (arg_storage != COREDUMP_STORAGE_NONE)
arg_storage = COREDUMP_STORAGE_EXTERNAL;
@@ -889,7 +999,8 @@ static int process_journald_crash(const char *context[], int input_fd) {
if (r < 0)
return r;
- log_info("Detected coredump of the journal daemon itself, diverted to %s.", filename);
+ log_notice("Detected coredump of the journal daemon or PID 1, diverted to %s.", filename);
+
return 0;
}
@@ -905,11 +1016,13 @@ static int process_kernel(int argc, char* argv[]) {
/* The larger ones we allocate on the heap */
_cleanup_free_ char
*core_owner_uid = NULL, *core_open_fds = NULL, *core_proc_status = NULL,
- *core_proc_maps = NULL, *core_proc_limits = NULL, *core_proc_cgroup = NULL, *core_environ = NULL;
+ *core_proc_maps = NULL, *core_proc_limits = NULL, *core_proc_cgroup = NULL, *core_environ = NULL,
+ *core_proc_mountinfo = NULL, *core_container_cmdline = NULL;
_cleanup_free_ char *exe = NULL, *comm = NULL;
const char *context[_CONTEXT_MAX];
- struct iovec iovec[25];
+ bool proc_self_root_is_slash;
+ struct iovec iovec[27];
size_t n_iovec = 0;
uid_t owner_uid;
const char *p;
@@ -949,9 +1062,17 @@ static int process_kernel(int argc, char* argv[]) {
if (cg_pid_get_unit(pid, &t) >= 0) {
- if (streq(t, SPECIAL_JOURNALD_SERVICE)) {
+ /* If this is PID 1 disable coredump collection, we'll unlikely be able to process it later on. */
+ if (streq(t, SPECIAL_INIT_SCOPE)) {
+ log_notice("Due to PID 1 having crashed coredump collection will now be turned off.");
+ (void) write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0);
+ }
+
+ /* Let's avoid dead-locks when processing journald and init crashes, as socket activation and logging
+ * are unlikely to work then. */
+ if (STR_IN_SET(t, SPECIAL_JOURNALD_SERVICE, SPECIAL_INIT_SCOPE)) {
free(t);
- return process_journald_crash(context, STDIN_FILENO);
+ return process_special_crash(context, STDIN_FILENO);
}
core_unit = strjoina("COREDUMP_UNIT=", t);
@@ -1074,6 +1195,15 @@ static int process_kernel(int argc, char* argv[]) {
IOVEC_SET_STRING(iovec[n_iovec++], core_proc_cgroup);
}
+ p = procfs_file_alloca(pid, "mountinfo");
+ if (read_full_file(p, &t, NULL) >=0) {
+ core_proc_mountinfo = strappend("COREDUMP_PROC_MOUNTINFO=", t);
+ free(t);
+
+ if (core_proc_mountinfo)
+ IOVEC_SET_STRING(iovec[n_iovec++], core_proc_mountinfo);
+ }
+
if (get_process_cwd(pid, &t) >= 0) {
core_cwd = strjoina("COREDUMP_CWD=", t);
free(t);
@@ -1083,9 +1213,20 @@ static int process_kernel(int argc, char* argv[]) {
if (get_process_root(pid, &t) >= 0) {
core_root = strjoina("COREDUMP_ROOT=", t);
- free(t);
IOVEC_SET_STRING(iovec[n_iovec++], core_root);
+
+ /* If the process' root is "/", then there is a chance it has
+ * mounted own root and hence being containerized. */
+ proc_self_root_is_slash = strcmp(t, "/") == 0;
+ free(t);
+ if (proc_self_root_is_slash && get_process_container_parent_cmdline(pid, &t) > 0) {
+ core_container_cmdline = strappend("COREDUMP_CONTAINER_CMDLINE=", t);
+ free(t);
+
+ if (core_container_cmdline)
+ IOVEC_SET_STRING(iovec[n_iovec++], core_container_cmdline);
+ }
}
if (get_process_environ(pid, &t) >= 0) {
diff --git a/src/dbus1-generator/dbus1-generator.c b/src/dbus1-generator/dbus1-generator.c
deleted file mode 100644
index 717cb9558e..0000000000
--- a/src/dbus1-generator/dbus1-generator.c
+++ /dev/null
@@ -1,331 +0,0 @@
-/***
- This file is part of systemd.
-
- Copyright 2013 Lennart Poettering
-
- systemd is free software; you can redistribute it and/or modify it
- under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- systemd is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#include "alloc-util.h"
-#include "bus-internal.h"
-#include "bus-util.h"
-#include "cgroup-util.h"
-#include "conf-parser.h"
-#include "dirent-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "mkdir.h"
-#include "special.h"
-#include "unit-name.h"
-#include "util.h"
-
-static const char *arg_dest_late = "/tmp", *arg_dest = "/tmp";
-
-static int create_dbus_files(
- const char *path,
- const char *name,
- const char *service,
- const char *exec,
- const char *user,
- const char *type) {
-
- _cleanup_free_ char *b = NULL, *s = NULL, *lnk = NULL;
- _cleanup_fclose_ FILE *f = NULL;
- int r;
-
- assert(path);
- assert(name);
- assert(service || exec);
-
- if (!service) {
- _cleanup_free_ char *a = NULL;
-
- s = strjoin("dbus-", name, ".service", NULL);
- if (!s)
- return log_oom();
-
- a = strjoin(arg_dest_late, "/", s, NULL);
- if (!a)
- return log_oom();
-
- f = fopen(a, "wxe");
- if (!f)
- return log_error_errno(errno, "Failed to create %s: %m", a);
-
- fprintf(f,
- "# Automatically generated by systemd-dbus1-generator\n\n"
- "[Unit]\n"
- "SourcePath=%s\n"
- "Description=DBUS1: %s\n"
- "Documentation=man:systemd-dbus1-generator(8)\n\n"
- "[Service]\n"
- "ExecStart=%s\n"
- "Type=dbus\n"
- "BusName=%s\n",
- path,
- name,
- exec,
- name);
-
- if (user)
- fprintf(f, "User=%s\n", user);
-
-
- if (type) {
- fprintf(f, "Environment=DBUS_STARTER_BUS_TYPE=%s\n", type);
-
- if (streq(type, "system"))
- fprintf(f, "Environment=DBUS_STARTER_ADDRESS=" DEFAULT_SYSTEM_BUS_ADDRESS "\n");
- else if (streq(type, "session")) {
- char *run;
-
- run = getenv("XDG_RUNTIME_DIR");
- if (!run) {
- log_error("XDG_RUNTIME_DIR not set.");
- return -EINVAL;
- }
-
- fprintf(f, "Environment=DBUS_STARTER_ADDRESS="KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT "\n",
- getuid(), run);
- }
- }
-
- r = fflush_and_check(f);
- if (r < 0)
- return log_error_errno(r, "Failed to write %s: %m", a);
-
- f = safe_fclose(f);
-
- service = s;
- }
-
- b = strjoin(arg_dest_late, "/", name, ".busname", NULL);
- if (!b)
- return log_oom();
-
- f = fopen(b, "wxe");
- if (!f)
- return log_error_errno(errno, "Failed to create %s: %m", b);
-
- fprintf(f,
- "# Automatically generated by systemd-dbus1-generator\n\n"
- "[Unit]\n"
- "SourcePath=%s\n"
- "Description=DBUS1: %s\n"
- "Documentation=man:systemd-dbus1-generator(8)\n\n"
- "[BusName]\n"
- "Name=%s\n"
- "Service=%s\n"
- "AllowWorld=talk\n",
- path,
- name,
- name,
- service);
-
- r = fflush_and_check(f);
- if (r < 0)
- return log_error_errno(r, "Failed to write %s: %m", b);
-
- lnk = strjoin(arg_dest_late, "/" SPECIAL_BUSNAMES_TARGET ".wants/", name, ".busname", NULL);
- if (!lnk)
- return log_oom();
-
- mkdir_parents_label(lnk, 0755);
- if (symlink(b, lnk))
- return log_error_errno(errno, "Failed to create symlink %s: %m", lnk);
-
- return 0;
-}
-
-static int add_dbus(const char *path, const char *fname, const char *type) {
- _cleanup_free_ char *name = NULL, *exec = NULL, *user = NULL, *service = NULL;
-
- const ConfigTableItem table[] = {
- { "D-BUS Service", "Name", config_parse_string, 0, &name },
- { "D-BUS Service", "Exec", config_parse_string, 0, &exec },
- { "D-BUS Service", "User", config_parse_string, 0, &user },
- { "D-BUS Service", "SystemdService", config_parse_string, 0, &service },
- { },
- };
-
- char *p;
- int r;
-
- assert(path);
- assert(fname);
-
- p = strjoina(path, "/", fname);
- r = config_parse(NULL, p, NULL,
- "D-BUS Service\0",
- config_item_table_lookup, table,
- true, false, true, NULL);
- if (r < 0)
- return r;
-
- if (!name) {
- log_warning("Activation file %s lacks name setting, ignoring.", p);
- return 0;
- }
-
- if (!service_name_is_valid(name)) {
- log_warning("Bus service name %s is not valid, ignoring.", name);
- return 0;
- }
-
- if (streq(name, "org.freedesktop.systemd1")) {
- log_debug("Skipping %s, identified as systemd.", p);
- return 0;
- }
-
- if (service) {
- if (!unit_name_is_valid(service, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) {
- log_warning("Unit name %s is not valid, ignoring.", service);
- return 0;
- }
- if (!endswith(service, ".service")) {
- log_warning("Bus names can only activate services, ignoring %s.", p);
- return 0;
- }
- } else {
- if (streq(exec, "/bin/false") || !exec) {
- log_warning("Neither service name nor binary path specified, ignoring %s.", p);
- return 0;
- }
-
- if (exec[0] != '/') {
- log_warning("Exec= in %s does not start with an absolute path, ignoring.", p);
- return 0;
- }
- }
-
- return create_dbus_files(p, name, service, exec, user, type);
-}
-
-static int parse_dbus_fragments(const char *path, const char *type) {
- _cleanup_closedir_ DIR *d = NULL;
- struct dirent *de;
- int r;
-
- assert(path);
- assert(type);
-
- d = opendir(path);
- if (!d) {
- if (errno == -ENOENT)
- return 0;
-
- return log_error_errno(errno, "Failed to enumerate D-Bus activated services: %m");
- }
-
- r = 0;
- FOREACH_DIRENT(de, d, goto fail) {
- int q;
-
- if (!endswith(de->d_name, ".service"))
- continue;
-
- q = add_dbus(path, de->d_name, type);
- if (q < 0)
- r = q;
- }
-
- return r;
-
-fail:
- return log_error_errno(errno, "Failed to read D-Bus services directory: %m");
-}
-
-static int link_busnames_target(const char *units) {
- const char *f, *t;
-
- f = strjoina(units, "/" SPECIAL_BUSNAMES_TARGET);
- t = strjoina(arg_dest, "/" SPECIAL_BASIC_TARGET ".wants/" SPECIAL_BUSNAMES_TARGET);
-
- mkdir_parents_label(t, 0755);
- if (symlink(f, t) < 0)
- return log_error_errno(errno, "Failed to create symlink %s: %m", t);
-
- return 0;
-}
-
-static int link_compatibility(const char *units) {
- const char *f, *t;
-
- f = strjoina(units, "/systemd-bus-proxyd.socket");
- t = strjoina(arg_dest, "/" SPECIAL_DBUS_SOCKET);
- mkdir_parents_label(t, 0755);
- if (symlink(f, t) < 0)
- return log_error_errno(errno, "Failed to create symlink %s: %m", t);
-
- f = strjoina(units, "/systemd-bus-proxyd.socket");
- t = strjoina(arg_dest, "/" SPECIAL_SOCKETS_TARGET ".wants/systemd-bus-proxyd.socket");
- mkdir_parents_label(t, 0755);
- if (symlink(f, t) < 0)
- return log_error_errno(errno, "Failed to create symlink %s: %m", t);
-
- t = strjoina(arg_dest, "/" SPECIAL_DBUS_SERVICE);
- if (symlink("/dev/null", t) < 0)
- return log_error_errno(errno, "Failed to mask %s: %m", t);
-
- return 0;
-}
-
-int main(int argc, char *argv[]) {
- const char *path, *type, *units;
- int r, q;
-
- if (argc > 1 && argc != 4) {
- log_error("This program takes three or no arguments.");
- return EXIT_FAILURE;
- }
-
- if (argc > 1) {
- arg_dest = argv[1];
- arg_dest_late = argv[3];
- }
-
- log_set_target(LOG_TARGET_SAFE);
- log_parse_environment();
- log_open();
-
- umask(0022);
-
- if (!is_kdbus_available())
- return 0;
-
- r = cg_pid_get_owner_uid(0, NULL);
- if (r >= 0) {
- path = "/usr/share/dbus-1/services";
- type = "session";
- units = USER_DATA_UNIT_PATH;
- } else if (r == -ENXIO) {
- path = "/usr/share/dbus-1/system-services";
- type = "system";
- units = SYSTEM_DATA_UNIT_PATH;
- } else
- return log_error_errno(r, "Failed to determine whether we are running as user or system instance: %m");
-
- r = parse_dbus_fragments(path, type);
-
- /* FIXME: One day this should just be pulled in statically from basic.target */
- q = link_busnames_target(units);
- if (q < 0)
- r = q;
-
- q = link_compatibility(units);
- if (q < 0)
- r = q;
-
- return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
-}
diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c
index 3df72460ef..c9e8e54ee3 100644
--- a/src/firstboot/firstboot.c
+++ b/src/firstboot/firstboot.c
@@ -427,7 +427,7 @@ static int process_machine_id(void) {
if (laccess(etc_machine_id, F_OK) >= 0)
return 0;
- if (sd_id128_equal(arg_machine_id, SD_ID128_NULL))
+ if (sd_id128_is_null(arg_machine_id))
return 0;
mkdir_parents(etc_machine_id, 0755);
diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c
index 108522873e..33af553d0d 100644
--- a/src/fstab-generator/fstab-generator.c
+++ b/src/fstab-generator/fstab-generator.c
@@ -85,13 +85,12 @@ static int add_swap(
return log_oom();
f = fopen(unit, "wxe");
- if (!f) {
- if (errno == EEXIST)
- log_error("Failed to create swap unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit);
- else
- log_error_errno(errno, "Failed to create unit file %s: %m", unit);
- return -errno;
- }
+ if (!f)
+ return log_error_errno(errno,
+ errno == EEXIST ?
+ "Failed to create swap unit file %s, as it already exists. Duplicate entry in /etc/fstab?" :
+ "Failed to create unit file %s: %m",
+ unit);
fprintf(f,
"# Automatically generated by systemd-fstab-generator\n\n"
@@ -281,13 +280,12 @@ static int add_mount(
return log_oom();
f = fopen(unit, "wxe");
- if (!f) {
- if (errno == EEXIST)
- log_error("Failed to create mount unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit);
- else
- log_error_errno(errno, "Failed to create unit file %s: %m", unit);
- return -errno;
- }
+ if (!f)
+ return log_error_errno(errno,
+ errno == EEXIST ?
+ "Failed to create mount unit file %s, as it already exists. Duplicate entry in /etc/fstab?" :
+ "Failed to create unit file %s: %m",
+ unit);
fprintf(f,
"# Automatically generated by systemd-fstab-generator\n\n"
@@ -417,8 +415,7 @@ static int parse_fstab(bool initrd) {
if (errno == ENOENT)
return 0;
- log_error_errno(errno, "Failed to open %s: %m", fstab_path);
- return -errno;
+ return log_error_errno(errno, "Failed to open %s: %m", fstab_path);
}
while ((me = getmntent(f))) {
@@ -496,6 +493,18 @@ static int add_sysroot_mount(void) {
return 0;
}
+ if (streq(arg_root_what, "gpt-auto")) {
+ /* This is handled by the gpt-auto generator */
+ log_debug("Skipping root directory handling, as gpt-auto was requested.");
+ return 0;
+ }
+
+ if (path_equal(arg_root_what, "/dev/nfs")) {
+ /* This is handled by the kernel or the initrd */
+ log_debug("Skipping root directory handling, as /dev/nfs was requested.");
+ return 0;
+ }
+
what = fstab_node_to_udev_node(arg_root_what);
if (!what)
return log_oom();
@@ -520,10 +529,10 @@ static int add_sysroot_mount(void) {
"/sysroot",
arg_root_fstype,
opts,
- is_device_path(what) ? 1 : 0,
- false,
- false,
- false,
+ is_device_path(what) ? 1 : 0, /* passno */
+ false, /* noauto off */
+ false, /* nofail off */
+ false, /* automount off */
SPECIAL_INITRD_ROOT_FS_TARGET,
"/proc/cmdline");
}
@@ -536,22 +545,20 @@ static int add_sysroot_usr_mount(void) {
return 0;
if (arg_root_what && !arg_usr_what) {
+ /* Copy over the root device, in case the /usr mount just differs in a mount option (consider btrfs subvolumes) */
arg_usr_what = strdup(arg_root_what);
-
if (!arg_usr_what)
return log_oom();
}
if (arg_root_fstype && !arg_usr_fstype) {
arg_usr_fstype = strdup(arg_root_fstype);
-
if (!arg_usr_fstype)
return log_oom();
}
if (arg_root_options && !arg_usr_options) {
arg_usr_options = strdup(arg_root_options);
-
if (!arg_usr_options)
return log_oom();
}
@@ -560,10 +567,8 @@ static int add_sysroot_usr_mount(void) {
return 0;
what = fstab_node_to_udev_node(arg_usr_what);
- if (!path_is_absolute(what)) {
- log_debug("Skipping entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype));
- return -1;
- }
+ if (!what)
+ return log_oom();
if (!arg_usr_options)
opts = arg_root_rw > 0 ? "rw" : "ro";
@@ -577,10 +582,10 @@ static int add_sysroot_usr_mount(void) {
"/sysroot/usr",
arg_usr_fstype,
opts,
- 1,
- false,
- false,
- false,
+ is_device_path(what) ? 1 : 0, /* passno */
+ false, /* noauto off */
+ false, /* nofail off */
+ false, /* automount off */
SPECIAL_INITRD_FS_TARGET,
"/proc/cmdline");
}
@@ -675,10 +680,15 @@ int main(int argc, char *argv[]) {
/* Always honour root= and usr= in the kernel command line if we are in an initrd */
if (in_initrd()) {
+ int k;
+
r = add_sysroot_mount();
- if (r == 0)
- r = add_sysroot_usr_mount();
- }
+
+ k = add_sysroot_usr_mount();
+ if (k < 0)
+ r = k;
+ } else
+ r = 0;
/* Honour /etc/fstab only when that's enabled */
if (arg_fstab_enabled) {
diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c
index a4938a7c3a..6cc1aad705 100644
--- a/src/gpt-auto-generator/gpt-auto-generator.c
+++ b/src/gpt-auto-generator/gpt-auto-generator.c
@@ -450,101 +450,101 @@ static int add_automount(
}
static int add_boot(const char *what) {
- _cleanup_blkid_free_probe_ blkid_probe b = NULL;
- const char *fstype = NULL, *uuid = NULL;
- sd_id128_t id, type_id;
+ const char *esp;
int r;
assert(what);
- if (!is_efi_boot()) {
- log_debug("Not an EFI boot, ignoring /boot.");
- return 0;
- }
-
if (in_initrd()) {
- log_debug("In initrd, ignoring /boot.");
+ log_debug("In initrd, ignoring the ESP.");
return 0;
}
if (detect_container() > 0) {
- log_debug("In a container, ignoring /boot.");
+ log_debug("In a container, ignoring the ESP.");
return 0;
}
+ /* If /efi exists we'll use that. Otherwise we'll use /boot, as that's usually the better choice */
+ esp = access("/efi/", F_OK) >= 0 ? "/efi" : "/boot";
+
/* We create an .automount which is not overridden by the .mount from the fstab generator. */
- if (fstab_is_mount_point("/boot")) {
- log_debug("/boot specified in fstab, ignoring.");
+ if (fstab_is_mount_point(esp)) {
+ log_debug("%s specified in fstab, ignoring.", esp);
return 0;
}
- if (path_is_busy("/boot")) {
- log_debug("/boot already populated, ignoring.");
+ if (path_is_busy(esp)) {
+ log_debug("%s already populated, ignoring.", esp);
return 0;
}
- r = efi_loader_get_device_part_uuid(&id);
- if (r == -ENOENT) {
- log_debug("EFI loader partition unknown.");
- return 0;
- }
+ if (is_efi_boot()) {
+ _cleanup_blkid_free_probe_ blkid_probe b = NULL;
+ const char *fstype = NULL, *uuid_string = NULL;
+ sd_id128_t loader_uuid, part_uuid;
- if (r < 0) {
- log_error_errno(r, "Failed to read ESP partition UUID: %m");
- return r;
- }
+ /* If this is an EFI boot, be extra careful, and only mount the ESP if it was the ESP used for booting. */
- errno = 0;
- b = blkid_new_probe_from_filename(what);
- if (!b) {
- if (errno == 0)
- return log_oom();
- return log_error_errno(errno, "Failed to allocate prober: %m");
- }
-
- blkid_probe_enable_partitions(b, 1);
- blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
+ r = efi_loader_get_device_part_uuid(&loader_uuid);
+ if (r == -ENOENT) {
+ log_debug("EFI loader partition unknown.");
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to read ESP partition UUID: %m");
- errno = 0;
- r = blkid_do_safeprobe(b);
- if (r == -2 || r == 1) /* no result or uncertain */
- return 0;
- else if (r != 0)
- return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what);
+ errno = 0;
+ b = blkid_new_probe_from_filename(what);
+ if (!b) {
+ if (errno == 0)
+ return log_oom();
+ return log_error_errno(errno, "Failed to allocate prober: %m");
+ }
- (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
- if (!streq_ptr(fstype, "vfat")) {
- log_debug("Partition for /boot is not a FAT filesystem, ignoring.");
- return 0;
- }
+ blkid_probe_enable_partitions(b, 1);
+ blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
- errno = 0;
- r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &uuid, NULL);
- if (r != 0) {
- log_debug_errno(errno, "Partition for /boot does not have a UUID, ignoring.");
- return 0;
- }
+ errno = 0;
+ r = blkid_do_safeprobe(b);
+ if (r == -2 || r == 1) /* no result or uncertain */
+ return 0;
+ else if (r != 0)
+ return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what);
- if (sd_id128_from_string(uuid, &type_id) < 0) {
- log_debug("Partition for /boot does not have a valid UUID, ignoring.");
- return 0;
- }
+ (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
+ if (!streq_ptr(fstype, "vfat")) {
+ log_debug("Partition for %s is not a FAT filesystem, ignoring.", esp);
+ return 0;
+ }
- if (!sd_id128_equal(type_id, id)) {
- log_debug("Partition for /boot does not appear to be the partition we are booted from.");
- return 0;
- }
+ errno = 0;
+ r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &uuid_string, NULL);
+ if (r != 0) {
+ log_debug_errno(errno, "Partition for %s does not have a UUID, ignoring.", esp);
+ return 0;
+ }
- r = add_automount("boot",
- what,
- "/boot",
- "vfat",
- true,
- "umask=0077",
- "EFI System Partition Automount",
- 120 * USEC_PER_SEC);
+ if (sd_id128_from_string(uuid_string, &part_uuid) < 0) {
+ log_debug("Partition for %s does not have a valid UUID, ignoring.", esp);
+ return 0;
+ }
- return r;
+ if (!sd_id128_equal(part_uuid, loader_uuid)) {
+ log_debug("Partition for %s does not appear to be the partition we are booted from.", esp);
+ return 0;
+ }
+ } else
+ log_debug("Not an EFI boot, skipping ESP check.");
+
+ return add_automount("boot",
+ what,
+ esp,
+ "vfat",
+ true,
+ "umask=0077",
+ "EFI System Partition Automount",
+ 120 * USEC_PER_SEC);
}
#else
static int add_boot(const char *what) {
diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c
index 1160dacdf1..e12cd93d1c 100644
--- a/src/hwdb/hwdb.c
+++ b/src/hwdb/hwdb.c
@@ -29,7 +29,9 @@
#include "fs-util.h"
#include "hwdb-internal.h"
#include "hwdb-util.h"
+#include "label.h"
#include "mkdir.h"
+#include "selinux-util.h"
#include "strbuf.h"
#include "string-util.h"
#include "strv.h"
@@ -643,12 +645,12 @@ static int hwdb_update(int argc, char *argv[], void *userdata) {
if (!hwdb_bin)
return -ENOMEM;
- mkdir_parents(hwdb_bin, 0755);
+ mkdir_parents_label(hwdb_bin, 0755);
r = trie_store(trie, hwdb_bin);
if (r < 0)
return log_error_errno(r, "Failure writing database %s: %m", hwdb_bin);
- return 0;
+ return label_fix(hwdb_bin, false, false);
}
static void help(void) {
@@ -732,6 +734,8 @@ int main (int argc, char *argv[]) {
if (r <= 0)
goto finish;
+ mac_selinux_init();
+
r = hwdb_main(argc, argv);
finish:
diff --git a/src/import/import-common.c b/src/import/import-common.c
index 287a3382a1..81209cdaf6 100644
--- a/src/import/import-common.c
+++ b/src/import/import-common.c
@@ -125,9 +125,7 @@ int import_fork_tar_x(const char *path, pid_t *ret) {
if (null_fd != STDOUT_FILENO)
null_fd = safe_close(null_fd);
- fd_cloexec(STDIN_FILENO, false);
- fd_cloexec(STDOUT_FILENO, false);
- fd_cloexec(STDERR_FILENO, false);
+ stdio_unset_cloexec();
if (unshare(CLONE_NEWNET) < 0)
log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
@@ -199,9 +197,7 @@ int import_fork_tar_c(const char *path, pid_t *ret) {
if (null_fd != STDIN_FILENO)
null_fd = safe_close(null_fd);
- fd_cloexec(STDIN_FILENO, false);
- fd_cloexec(STDOUT_FILENO, false);
- fd_cloexec(STDERR_FILENO, false);
+ stdio_unset_cloexec();
if (unshare(CLONE_NEWNET) < 0)
log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
diff --git a/src/import/import.c b/src/import/import.c
index 4e442ee84a..2b6ca24af8 100644
--- a/src/import/import.c
+++ b/src/import/import.c
@@ -90,7 +90,7 @@ static int import_tar(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
else if (r > 0) {
- log_error_errno(EEXIST, "Image '%s' already exists.", local);
+ log_error("Image '%s' already exists.", local);
return -EEXIST;
}
}
@@ -185,7 +185,7 @@ static int import_raw(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
else if (r > 0) {
- log_error_errno(EEXIST, "Image '%s' already exists.", local);
+ log_error("Image '%s' already exists.", local);
return -EEXIST;
}
}
diff --git a/src/import/importd.c b/src/import/importd.c
index 956a82945c..28b4302cb3 100644
--- a/src/import/importd.c
+++ b/src/import/importd.c
@@ -448,9 +448,7 @@ static int transfer_start(Transfer *t) {
safe_close(null_fd);
}
- fd_cloexec(STDIN_FILENO, false);
- fd_cloexec(STDOUT_FILENO, false);
- fd_cloexec(STDERR_FILENO, false);
+ stdio_unset_cloexec();
setenv("SYSTEMD_LOG_TARGET", "console-prefixed", 1);
setenv("NOTIFY_SOCKET", "/run/systemd/import/notify", 1);
diff --git a/src/import/pull-common.c b/src/import/pull-common.c
index dc4e4667a9..2ae2a4174c 100644
--- a/src/import/pull-common.c
+++ b/src/import/pull-common.c
@@ -506,9 +506,7 @@ int pull_verify(PullJob *main_job,
cmd[k++] = "-";
cmd[k++] = NULL;
- fd_cloexec(STDIN_FILENO, false);
- fd_cloexec(STDOUT_FILENO, false);
- fd_cloexec(STDERR_FILENO, false);
+ stdio_unset_cloexec();
execvp("gpg2", (char * const *) cmd);
execvp("gpg", (char * const *) cmd);
diff --git a/src/import/pull.c b/src/import/pull.c
index 72604a6a74..53b1211965 100644
--- a/src/import/pull.c
+++ b/src/import/pull.c
@@ -97,7 +97,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
else if (r > 0) {
- log_error_errno(EEXIST, "Image '%s' already exists.", local);
+ log_error("Image '%s' already exists.", local);
return -EEXIST;
}
}
@@ -183,7 +183,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
else if (r > 0) {
- log_error_errno(EEXIST, "Image '%s' already exists.", local);
+ log_error("Image '%s' already exists.", local);
return -EEXIST;
}
}
diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index 35a1e55f9e..f1ef90ed7a 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -1564,7 +1564,7 @@ int main(int argc, char **argv) {
if (r < 0)
log_error_errno(r, "Failed to enable watchdog: %m");
else
- log_debug("Watchdog is %s.", r > 0 ? "enabled" : "disabled");
+ log_debug("Watchdog is %sd.", enable_disable(r > 0));
log_debug("%s running as pid "PID_FMT,
program_invocation_short_name, getpid());
diff --git a/src/journal-remote/microhttpd-util.c b/src/journal-remote/microhttpd-util.c
index c65c43186f..2f16b02e9a 100644
--- a/src/journal-remote/microhttpd-util.c
+++ b/src/journal-remote/microhttpd-util.c
@@ -60,7 +60,7 @@ static int mhd_respond_internal(struct MHD_Connection *connection,
if (!response)
return MHD_NO;
- log_debug("Queing response %u: %s", code, buffer);
+ log_debug("Queueing response %u: %s", code, buffer);
MHD_add_response_header(response, "Content-Type", "text/plain");
r = MHD_queue_response(connection, code, response);
MHD_destroy_response(response);
diff --git a/src/journal/.gitignore b/src/journal/.gitignore
index 04d5852547..b93a9462fa 100644
--- a/src/journal/.gitignore
+++ b/src/journal/.gitignore
@@ -1,4 +1,3 @@
/journald-gperf.c
-/libsystemd-journal.pc
/audit_type-list.txt
/audit_type-*-name.*
diff --git a/src/journal/journal-send.c b/src/journal/journal-send.c
index 5e8a3e3200..440fba67ca 100644
--- a/src/journal/journal-send.c
+++ b/src/journal/journal-send.c
@@ -107,6 +107,13 @@ _public_ int sd_journal_printv(int priority, const char *format, va_list ap) {
memcpy(buffer, "MESSAGE=", 8);
vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap);
+ /* Strip trailing whitespace, keep prefix whitespace. */
+ (void) strstrip(buffer);
+
+ /* Suppress empty lines */
+ if (isempty(buffer+8))
+ return 0;
+
zero(iov);
IOVEC_SET_STRING(iov[0], buffer);
IOVEC_SET_STRING(iov[1], p);
@@ -158,6 +165,8 @@ _printf_(1, 0) static int fill_iovec_sprintf(const char *format, va_list ap, int
VA_FORMAT_ADVANCE(format, ap);
+ (void) strstrip(buffer); /* strip trailing whitespace, keep prefixing whitespace */
+
IOVEC_SET_STRING(iov[i++], buffer);
format = va_arg(ap, char *);
@@ -471,6 +480,13 @@ _public_ int sd_journal_printv_with_location(int priority, const char *file, con
memcpy(buffer, "MESSAGE=", 8);
vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap);
+ /* Strip trailing whitespace, keep prefixing whitespace */
+ (void) strstrip(buffer);
+
+ /* Suppress empty lines */
+ if (isempty(buffer+8))
+ return 0;
+
/* func is initialized from __func__ which is not a macro, but
* a static const char[], hence cannot easily be prefixed with
* CODE_FUNC=, hence let's do it manually here. */
diff --git a/src/journal/journal-verify.c b/src/journal/journal-verify.c
index a37316b8f9..f61f158e8a 100644
--- a/src/journal/journal-verify.c
+++ b/src/journal/journal-verify.c
@@ -26,6 +26,7 @@
#include "compress.h"
#include "fd-util.h"
#include "fileio.h"
+#include "fs-util.h"
#include "journal-authenticate.h"
#include "journal-def.h"
#include "journal-file.h"
@@ -825,6 +826,8 @@ int journal_file_verify(
int data_fd = -1, entry_fd = -1, entry_array_fd = -1;
unsigned i;
bool found_last = false;
+ _cleanup_free_ char *tmp_dir = NULL;
+
#ifdef HAVE_GCRYPT
uint64_t last_tag = 0;
#endif
@@ -843,19 +846,25 @@ int journal_file_verify(
} else if (f->seal)
return -ENOKEY;
- data_fd = open_tmpfile_unlinkable("/var/tmp", O_RDWR | O_CLOEXEC);
+ r = var_tmp(&tmp_dir);
+ if (r < 0) {
+ log_error_errno(r, "Failed to determine temporary directory: %m");
+ goto fail;
+ }
+
+ data_fd = open_tmpfile_unlinkable(tmp_dir, O_RDWR | O_CLOEXEC);
if (data_fd < 0) {
r = log_error_errno(data_fd, "Failed to create data file: %m");
goto fail;
}
- entry_fd = open_tmpfile_unlinkable("/var/tmp", O_RDWR | O_CLOEXEC);
+ entry_fd = open_tmpfile_unlinkable(tmp_dir, O_RDWR | O_CLOEXEC);
if (entry_fd < 0) {
r = log_error_errno(entry_fd, "Failed to create entry file: %m");
goto fail;
}
- entry_array_fd = open_tmpfile_unlinkable("/var/tmp", O_RDWR | O_CLOEXEC);
+ entry_array_fd = open_tmpfile_unlinkable(tmp_dir, O_RDWR | O_CLOEXEC);
if (entry_array_fd < 0) {
r = log_error_errno(entry_array_fd,
"Failed to create entry array file: %m");
diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c
index 8e4897831b..6f841efb69 100644
--- a/src/journal/journalctl.c
+++ b/src/journal/journalctl.c
@@ -297,9 +297,9 @@ static void help(void) {
" -n --lines[=INTEGER] Number of journal entries to show\n"
" --no-tail Show all lines, even in follow mode\n"
" -r --reverse Show the newest entries first\n"
- " -o --output=STRING Change journal output mode (short, short-iso,\n"
- " short-precise, short-monotonic, verbose,\n"
- " export, json, json-pretty, json-sse, cat)\n"
+ " -o --output=STRING Change journal output mode (short, short-precise,\n"
+ " short-iso, short-full, short-monotonic, short-unix,\n"
+ " verbose, export, json, json-pretty, json-sse, cat)\n"
" --utc Express time in Coordinated Universal Time (UTC)\n"
" -x --catalog Add message explanations where available\n"
" --no-full Ellipsize fields\n"
@@ -348,6 +348,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_NO_FULL,
ARG_NO_TAIL,
ARG_NEW_ID128,
+ ARG_THIS_BOOT,
ARG_LIST_BOOTS,
ARG_USER,
ARG_SYSTEM,
@@ -392,9 +393,9 @@ static int parse_argv(int argc, char *argv[]) {
{ "new-id128", no_argument, NULL, ARG_NEW_ID128 },
{ "quiet", no_argument, NULL, 'q' },
{ "merge", no_argument, NULL, 'm' },
+ { "this-boot", no_argument, NULL, ARG_THIS_BOOT }, /* deprecated */
{ "boot", optional_argument, NULL, 'b' },
{ "list-boots", no_argument, NULL, ARG_LIST_BOOTS },
- { "this-boot", optional_argument, NULL, 'b' }, /* deprecated */
{ "dmesg", no_argument, NULL, 'k' },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
@@ -544,6 +545,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_merge = true;
break;
+ case ARG_THIS_BOOT:
+ arg_boot = true;
+ break;
+
case 'b':
arg_boot = true;
@@ -868,8 +873,8 @@ static int parse_argv(int argc, char *argv[]) {
return -EINVAL;
}
- if ((arg_boot || arg_action == ACTION_LIST_BOOTS) && (arg_file || arg_directory || arg_merge)) {
- log_error("Using --boot or --list-boots with --file, --directory or --merge is not supported.");
+ if ((arg_boot || arg_action == ACTION_LIST_BOOTS) && arg_merge) {
+ log_error("Using --boot or --list-boots with --merge is not supported.");
return -EINVAL;
}
@@ -1102,13 +1107,13 @@ static int discover_next_boot(sd_journal *j,
static int get_boots(
sd_journal *j,
BootId **boots,
- sd_id128_t *query_ref_boot,
- int ref_boot_offset) {
+ sd_id128_t *boot_id,
+ int offset) {
bool skip_once;
int r, count = 0;
BootId *head = NULL, *tail = NULL;
- const bool advance_older = query_ref_boot && ref_boot_offset <= 0;
+ const bool advance_older = boot_id && offset <= 0;
sd_id128_t previous_boot_id;
assert(j);
@@ -1116,19 +1121,19 @@ static int get_boots(
/* Adjust for the asymmetry that offset 0 is
* the last (and current) boot, while 1 is considered the
* (chronological) first boot in the journal. */
- skip_once = query_ref_boot && sd_id128_is_null(*query_ref_boot) && ref_boot_offset < 0;
+ skip_once = boot_id && sd_id128_is_null(*boot_id) && offset <= 0;
/* Advance to the earliest/latest occurrence of our reference
* boot ID (taking our lookup direction into account), so that
* discover_next_boot() can do its job.
* If no reference is given, the journal head/tail will do,
* they're "virtual" boots after all. */
- if (query_ref_boot && !sd_id128_is_null(*query_ref_boot)) {
+ if (boot_id && !sd_id128_is_null(*boot_id)) {
char match[9+32+1] = "_BOOT_ID=";
sd_journal_flush_matches(j);
- sd_id128_to_string(*query_ref_boot, match + 9);
+ sd_id128_to_string(*boot_id, match + 9);
r = sd_journal_add_match(j, match, sizeof(match) - 1);
if (r < 0)
return r;
@@ -1148,7 +1153,7 @@ static int get_boots(
return r;
else if (r == 0)
goto finish;
- else if (ref_boot_offset == 0) {
+ else if (offset == 0) {
count = 1;
goto finish;
}
@@ -1187,14 +1192,14 @@ static int get_boots(
previous_boot_id = current->id;
- if (query_ref_boot) {
+ if (boot_id) {
if (!skip_once)
- ref_boot_offset += advance_older ? 1 : -1;
+ offset += advance_older ? 1 : -1;
skip_once = false;
- if (ref_boot_offset == 0) {
+ if (offset == 0) {
count = 1;
- *query_ref_boot = current->id;
+ *boot_id = current->id;
break;
}
} else {
@@ -1250,7 +1255,7 @@ static int list_boots(sd_journal *j) {
static int add_boot(sd_journal *j) {
char match[9+32+1] = "_BOOT_ID=";
- sd_id128_t ref_boot_id;
+ sd_id128_t boot_id;
int r;
assert(j);
@@ -1258,11 +1263,16 @@ static int add_boot(sd_journal *j) {
if (!arg_boot)
return 0;
- if (arg_boot_offset == 0 && sd_id128_equal(arg_boot_id, SD_ID128_NULL))
+ /* Take a shortcut and use the current boot_id, which we can do very quickly.
+ * We can do this only when we logs are coming from the current machine,
+ * so take the slow path if log location is specified. */
+ if (arg_boot_offset == 0 && sd_id128_is_null(arg_boot_id) &&
+ !arg_directory && !arg_file)
+
return add_match_this_boot(j, arg_machine);
- ref_boot_id = arg_boot_id;
- r = get_boots(j, NULL, &ref_boot_id, arg_boot_offset);
+ boot_id = arg_boot_id;
+ r = get_boots(j, NULL, &boot_id, arg_boot_offset);
assert(r <= 1);
if (r <= 0) {
const char *reason = (r == 0) ? "No such boot ID in journal" : strerror(-r);
@@ -1277,7 +1287,7 @@ static int add_boot(sd_journal *j) {
return r == 0 ? -ENODATA : r;
}
- sd_id128_to_string(ref_boot_id, match + 9);
+ sd_id128_to_string(boot_id, match + 9);
r = sd_journal_add_match(j, match, sizeof(match) - 1);
if (r < 0)
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
index 8f82d2a838..587c343b31 100644
--- a/src/journal/journald-server.c
+++ b/src/journal/journald-server.c
@@ -727,7 +727,7 @@ static void dispatch_message_real(
*((char*) mempcpy(stpcpy(x, "_SELINUX_CONTEXT="), label, label_len)) = 0;
IOVEC_SET_STRING(iovec[n++], x);
} else {
- security_context_t con;
+ char *con;
if (getpidcon(ucred->pid, &con) >= 0) {
x = strjoina("_SELINUX_CONTEXT=", con);
@@ -877,7 +877,7 @@ void server_driver_message(Server *s, sd_id128_t message_id, const char *format,
assert_cc(6 == LOG_INFO);
IOVEC_SET_STRING(iovec[n++], "PRIORITY=6");
- if (!sd_id128_equal(message_id, SD_ID128_NULL)) {
+ if (!sd_id128_is_null(message_id)) {
snprintf(mid, sizeof(mid), LOG_MESSAGE_ID(message_id));
IOVEC_SET_STRING(iovec[n++], mid);
}
@@ -1607,7 +1607,7 @@ static int dispatch_notify_event(sd_event_source *es, int fd, uint32_t revents,
/* Dispatch one stream notification event */
stdout_stream_send_notify(s->stdout_streams_notify_queue);
- /* Leave us enabled if there's still more to to do. */
+ /* Leave us enabled if there's still more to do. */
if (s->send_watchdog || s->stdout_streams_notify_queue)
return 0;
diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h
index e025a4cf90..d2a32ab422 100644
--- a/src/journal/journald-server.h
+++ b/src/journal/journald-server.h
@@ -43,7 +43,7 @@ typedef enum Storage {
typedef enum SplitMode {
SPLIT_UID,
- SPLIT_LOGIN,
+ SPLIT_LOGIN, /* deprecated */
SPLIT_NONE,
_SPLIT_MAX,
_SPLIT_INVALID = -1
diff --git a/src/journal/mmap-cache.c b/src/journal/mmap-cache.c
index 6bcd9b6ac8..293d27053a 100644
--- a/src/journal/mmap-cache.c
+++ b/src/journal/mmap-cache.c
@@ -481,7 +481,7 @@ static int mmap_try_harder(MMapCache *m, void *addr, int fd, int prot, int flags
if (ptr != MAP_FAILED)
break;
if (errno != ENOMEM)
- return -errno;
+ return negative_errno();
r = make_room(m);
if (r < 0)
@@ -571,7 +571,7 @@ static int add_mmap(
return 1;
outofmem:
- munmap(d, wsize);
+ (void) munmap(d, wsize);
return -ENOMEM;
}
diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c
index 1cea68ad42..75a0ffb49b 100644
--- a/src/journal/sd-journal.c
+++ b/src/journal/sd-journal.c
@@ -1438,7 +1438,7 @@ static int add_directory(sd_journal *j, const char *prefix, const char *dirname)
if (j->toplevel_fd < 0)
d = opendir(path);
else
- /* Open the specified directory relative to the the toplevel fd. Enforce that the path specified is
+ /* Open the specified directory relative to the toplevel fd. Enforce that the path specified is
* relative, by dropping the initial slash */
d = xopendirat(j->toplevel_fd, skip_slash(path), 0);
if (!d) {
diff --git a/src/kernel-install/90-loaderentry.install b/src/kernel-install/90-loaderentry.install
index 4c9b1f0327..a0bca05c9a 100644
--- a/src/kernel-install/90-loaderentry.install
+++ b/src/kernel-install/90-loaderentry.install
@@ -16,7 +16,8 @@ if ! [[ $MACHINE_ID ]]; then
fi
BOOT_DIR="/$MACHINE_ID/$KERNEL_VERSION"
-LOADER_ENTRY="/boot/loader/entries/$MACHINE_ID-$KERNEL_VERSION.conf"
+BOOT_ROOT=${BOOT_DIR_ABS%$BOOT_DIR}
+LOADER_ENTRY="$BOOT_ROOT/loader/entries/$MACHINE_ID-$KERNEL_VERSION.conf"
if [[ $COMMAND == remove ]]; then
exec rm -f "$LOADER_ENTRY"
diff --git a/src/kernel-install/kernel-install b/src/kernel-install/kernel-install
index 3ae1d77e33..c66bcfc092 100644
--- a/src/kernel-install/kernel-install
+++ b/src/kernel-install/kernel-install
@@ -86,7 +86,20 @@ if [[ ! $COMMAND ]] || [[ ! $KERNEL_VERSION ]]; then
exit 1
fi
-BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION"
+if [[ -d /efi/loader/entries ]] || [[ -d /efi/$MACHINE_ID ]]; then
+ BOOT_DIR_ABS="/efi/$MACHINE_ID/$KERNEL_VERSION"
+elif [[ -d /boot/loader/entries ]] || [[ -d /boot/$MACHINE_ID ]]; then
+ BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION"
+elif [[ -d /boot/efi/loader/entries ]] || [[ -d /boot/efi/$MACHINE_ID ]]; then
+ BOOT_DIR_ABS="/boot/efi/$MACHINE_ID/$KERNEL_VERSION"
+elif mountpoint -q /efi; then
+ BOOT_DIR_ABS="/efi/$MACHINE_ID/$KERNEL_VERSION"
+elif mountpoint -q /boot/efi; then
+ BOOT_DIR_ABS="/boot/efi/$MACHINE_ID/$KERNEL_VERSION"
+else
+ BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION"
+fi
+
ret=0
readarray -t PLUGINS < <(
diff --git a/src/libsystemd-network/arp-util.c b/src/libsystemd-network/arp-util.c
index 4660c7ea09..02028bf28a 100644
--- a/src/libsystemd-network/arp-util.c
+++ b/src/libsystemd-network/arp-util.c
@@ -79,7 +79,7 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_
};
union sockaddr_union link = {
.ll.sll_family = AF_PACKET,
- .ll.sll_protocol = htons(ETH_P_ARP),
+ .ll.sll_protocol = htobe16(ETH_P_ARP),
.ll.sll_ifindex = ifindex,
.ll.sll_halen = ETH_ALEN,
.ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
@@ -112,17 +112,17 @@ static int arp_send_packet(int fd, int ifindex,
bool announce) {
union sockaddr_union link = {
.ll.sll_family = AF_PACKET,
- .ll.sll_protocol = htons(ETH_P_ARP),
+ .ll.sll_protocol = htobe16(ETH_P_ARP),
.ll.sll_ifindex = ifindex,
.ll.sll_halen = ETH_ALEN,
.ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
};
struct ether_arp arp = {
- .ea_hdr.ar_hrd = htons(ARPHRD_ETHER), /* HTYPE */
- .ea_hdr.ar_pro = htons(ETHERTYPE_IP), /* PTYPE */
+ .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */
+ .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */
.ea_hdr.ar_hln = ETH_ALEN, /* HLEN */
.ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */
- .ea_hdr.ar_op = htons(ARPOP_REQUEST), /* REQUEST */
+ .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */
};
int r;
diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c
index fac25e0fa2..a9f5a0a5de 100644
--- a/src/libsystemd-network/dhcp-network.c
+++ b/src/libsystemd-network/dhcp-network.c
@@ -107,9 +107,9 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
return -errno;
link->ll.sll_family = AF_PACKET;
- link->ll.sll_protocol = htons(ETH_P_IP);
+ link->ll.sll_protocol = htobe16(ETH_P_IP);
link->ll.sll_ifindex = ifindex;
- link->ll.sll_hatype = htons(arp_type);
+ link->ll.sll_hatype = htobe16(arp_type);
link->ll.sll_halen = mac_addr_len;
memcpy(link->ll.sll_addr, bcast_addr, mac_addr_len);
diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c
index d81e9ebd88..c2e4b0e9e3 100644
--- a/src/libsystemd-network/icmp6-util.c
+++ b/src/libsystemd-network/icmp6-util.c
@@ -49,7 +49,8 @@ int icmp6_bind_router_solicitation(int index) {
};
_cleanup_close_ int s = -1;
char ifname[IF_NAMESIZE] = "";
- int r, zero = 0, one = 1, hops = 255;
+ static const int zero = 0, one = 1, hops = 255;
+ int r;
s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6);
if (s < 0)
@@ -85,6 +86,10 @@ int icmp6_bind_router_solicitation(int index) {
if (r < 0)
return -errno;
+ r = setsockopt(s, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
+ if (r < 0)
+ return -errno;
+
if (if_indextoname(index, ifname) == 0)
return -errno;
diff --git a/src/libsystemd-network/lldp-internal.h b/src/libsystemd-network/lldp-internal.h
index 7592bc4305..becc162fab 100644
--- a/src/libsystemd-network/lldp-internal.h
+++ b/src/libsystemd-network/lldp-internal.h
@@ -28,6 +28,8 @@
#include "prioq.h"
struct sd_lldp {
+ unsigned n_ref;
+
int ifindex;
int fd;
diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c
index 6a716430e3..53e29377b3 100644
--- a/src/libsystemd-network/lldp-neighbor.c
+++ b/src/libsystemd-network/lldp-neighbor.c
@@ -197,7 +197,7 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
assert(n);
if (n->raw_size < sizeof(struct ether_header)) {
- log_lldp("Recieved truncated packet, ignoring.");
+ log_lldp("Received truncated packet, ignoring.");
return -EBADMSG;
}
@@ -360,9 +360,16 @@ end_marker:
void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) {
assert(n);
- if (n->ttl > 0)
- n->until = usec_add(now(clock_boottime_or_monotonic()), n->ttl * USEC_PER_SEC);
- else
+ if (n->ttl > 0) {
+ usec_t base;
+
+ /* Use the packet's timestamp if there is one known */
+ base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic());
+ if (base <= 0 || base == USEC_INFINITY)
+ base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */
+
+ n->until = usec_add(base, n->ttl * USEC_PER_SEC);
+ } else
n->until = 0;
if (n->lldp)
@@ -588,11 +595,11 @@ done:
return 0;
}
-_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret) {
+_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) {
assert_return(n, -EINVAL);
- assert_return(ret, -EINVAL);
+ assert_return(ret_sec, -EINVAL);
- *ret = n->ttl;
+ *ret_sec = n->ttl;
return 0;
}
@@ -651,7 +658,7 @@ _public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint
return 0;
}
-int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
+_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
_cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
int r;
@@ -668,7 +675,7 @@ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t ra
return r;
*ret = n;
- n = 0;
+ n = NULL;
return r;
}
@@ -679,7 +686,7 @@ _public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) {
assert(n->raw_size >= sizeof(struct ether_header));
n->rindex = sizeof(struct ether_header);
- return 0;
+ return n->rindex < n->raw_size;
}
_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) {
@@ -693,7 +700,7 @@ _public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) {
if (n->rindex + 2 > n->raw_size) /* Truncated message */
return -EBADMSG;
- length = LLDP_NEIGHBOR_LENGTH(n);
+ length = LLDP_NEIGHBOR_TLV_LENGTH(n);
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
@@ -711,7 +718,7 @@ _public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) {
if (n->rindex + 2 > n->raw_size)
return -EBADMSG;
- *type = LLDP_NEIGHBOR_TYPE(n);
+ *type = LLDP_NEIGHBOR_TLV_TYPE(n);
return 0;
}
@@ -743,14 +750,14 @@ _public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], u
if (r == 0)
return -ENXIO;
- length = LLDP_NEIGHBOR_LENGTH(n);
+ length = LLDP_NEIGHBOR_TLV_LENGTH(n);
if (length < 4)
return -EBADMSG;
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
- d = LLDP_NEIGHBOR_DATA(n);
+ d = LLDP_NEIGHBOR_TLV_DATA(n);
memcpy(oui, d, 3);
*subtype = d[3];
@@ -782,8 +789,7 @@ _public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret,
if (n->rindex + 2 > n->raw_size)
return -EBADMSG;
- length = LLDP_NEIGHBOR_LENGTH(n);
-
+ length = LLDP_NEIGHBOR_TLV_LENGTH(n);
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
@@ -792,3 +798,16 @@ _public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret,
return 0;
}
+
+_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) {
+ assert_return(n, -EINVAL);
+ assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
+ assert_return(clock_supported(clock), -EOPNOTSUPP);
+ assert_return(ret, -EINVAL);
+
+ if (!triple_timestamp_is_set(&n->timestamp))
+ return -ENODATA;
+
+ *ret = triple_timestamp_by_clock(&n->timestamp, clock);
+ return 0;
+}
diff --git a/src/libsystemd-network/lldp-neighbor.h b/src/libsystemd-network/lldp-neighbor.h
index f203bfa604..c1a7606d06 100644
--- a/src/libsystemd-network/lldp-neighbor.h
+++ b/src/libsystemd-network/lldp-neighbor.h
@@ -43,6 +43,8 @@ struct sd_lldp_neighbor {
sd_lldp *lldp;
unsigned n_ref;
+ triple_timestamp timestamp;
+
usec_t until;
unsigned prioq_idx;
@@ -81,18 +83,18 @@ static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) {
return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor));
}
-static inline uint8_t LLDP_NEIGHBOR_TYPE(const sd_lldp_neighbor *n) {
+static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) {
return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1;
}
-static inline size_t LLDP_NEIGHBOR_LENGTH(const sd_lldp_neighbor *n) {
+static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) {
uint8_t *p;
p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
return p[1] + (((size_t) (p[0] & 1)) << 8);
}
-static inline void* LLDP_NEIGHBOR_DATA(const sd_lldp_neighbor *n) {
+static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) {
return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2;
}
diff --git a/src/libsystemd-network/lldp-network.c b/src/libsystemd-network/lldp-network.c
index f031760351..59c25598e9 100644
--- a/src/libsystemd-network/lldp-network.c
+++ b/src/libsystemd-network/lldp-network.c
@@ -57,7 +57,8 @@ int lldp_network_bind_raw_socket(int ifindex) {
assert(ifindex > 0);
- fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, htons(ETHERTYPE_LLDP));
+ fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK,
+ htobe16(ETHERTYPE_LLDP));
if (fd < 0)
return -errno;
diff --git a/src/libsystemd-network/ndisc-internal.h b/src/libsystemd-network/ndisc-internal.h
new file mode 100644
index 0000000000..60e183ff8c
--- /dev/null
+++ b/src/libsystemd-network/ndisc-internal.h
@@ -0,0 +1,49 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "log.h"
+
+#include "sd-ndisc.h"
+
+struct sd_ndisc {
+ unsigned n_ref;
+
+ int ifindex;
+ int fd;
+
+ sd_event *event;
+ int event_priority;
+
+ struct ether_addr mac_addr;
+ uint8_t hop_limit;
+ uint32_t mtu;
+
+ sd_event_source *recv_event_source;
+ sd_event_source *timeout_event_source;
+
+ unsigned nd_sent;
+
+ sd_ndisc_callback_t callback;
+ void *userdata;
+};
+
+#define log_ndisc_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "NDISC: " fmt, ##__VA_ARGS__)
+#define log_ndisc(fmt, ...) log_ndisc_errno(0, fmt, ##__VA_ARGS__)
diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c
new file mode 100644
index 0000000000..d9950b638c
--- /dev/null
+++ b/src/libsystemd-network/ndisc-router.c
@@ -0,0 +1,779 @@
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <netinet/icmp6.h>
+
+#include "sd-ndisc.h"
+
+#include "alloc-util.h"
+#include "dns-domain.h"
+#include "hostname-util.h"
+#include "missing.h"
+#include "ndisc-internal.h"
+#include "ndisc-router.h"
+#include "strv.h"
+
+_public_ sd_ndisc_router* sd_ndisc_router_ref(sd_ndisc_router *rt) {
+ if (!rt)
+ return NULL;
+
+ assert(rt->n_ref > 0);
+ rt->n_ref++;
+
+ return rt;
+}
+
+_public_ sd_ndisc_router* sd_ndisc_router_unref(sd_ndisc_router *rt) {
+ if (!rt)
+ return NULL;
+
+ assert(rt->n_ref > 0);
+ rt->n_ref--;
+
+ if (rt->n_ref > 0)
+ return NULL;
+
+ free(rt);
+ return NULL;
+}
+
+sd_ndisc_router *ndisc_router_new(size_t raw_size) {
+ sd_ndisc_router *rt;
+
+ rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size);
+ if (!rt)
+ return NULL;
+
+ rt->raw_size = raw_size;
+ rt->n_ref = 1;
+
+ return rt;
+}
+
+_public_ int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size) {
+ _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+ assert_return(raw || raw_size <= 0, -EINVAL);
+
+ rt = ndisc_router_new(raw_size);
+ if (!rt)
+ return -ENOMEM;
+
+ memcpy(NDISC_ROUTER_RAW(rt), raw, raw_size);
+ r = ndisc_router_parse(rt);
+ if (r < 0)
+ return r;
+
+ *ret = rt;
+ rt = NULL;
+
+ return r;
+}
+
+_public_ int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret_addr, -EINVAL);
+
+ if (in6_addr_is_null(&rt->address))
+ return -ENODATA;
+
+ *ret_addr = rt->address;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
+ assert_return(clock_supported(clock), -EOPNOTSUPP);
+ assert_return(ret, -EINVAL);
+
+ if (!triple_timestamp_is_set(&rt->timestamp))
+ return -ENODATA;
+
+ *ret = triple_timestamp_by_clock(&rt->timestamp, clock);
+ return 0;
+}
+
+_public_ int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ *ret = NDISC_ROUTER_RAW(rt);
+ *size = rt->raw_size;
+
+ return 0;
+}
+
+int ndisc_router_parse(sd_ndisc_router *rt) {
+ struct nd_router_advert *a;
+ const uint8_t *p;
+ bool has_mtu = false, has_flag_extension = false;
+ size_t left;
+
+ assert(rt);
+
+ if (rt->raw_size < sizeof(struct nd_router_advert)) {
+ log_ndisc("Too small to be a router advertisement, ignoring.");
+ return -EBADMSG;
+ }
+
+ /* Router advertisement packets are neatly aligned to 64bit boundaries, hence we can access them directly */
+ a = NDISC_ROUTER_RAW(rt);
+
+ if (a->nd_ra_type != ND_ROUTER_ADVERT) {
+ log_ndisc("Received ND packet that is not a router advertisement, ignoring.");
+ return -EBADMSG;
+ }
+
+ if (a->nd_ra_code != 0) {
+ log_ndisc("Received ND packet with wrong RA code, ignoring.");
+ return -EBADMSG;
+ }
+
+ rt->hop_limit = a->nd_ra_curhoplimit;
+ rt->flags = a->nd_ra_flags_reserved; /* the first 8bit */
+ rt->lifetime = be16toh(a->nd_ra_router_lifetime);
+
+ rt->preference = (rt->flags >> 3) & 3;
+ if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
+ rt->preference = SD_NDISC_PREFERENCE_MEDIUM;
+
+ p = (const uint8_t*) NDISC_ROUTER_RAW(rt) + sizeof(struct nd_router_advert);
+ left = rt->raw_size - sizeof(struct nd_router_advert);
+
+ for (;;) {
+ uint8_t type;
+ size_t length;
+
+ if (left == 0)
+ break;
+
+ if (left < 2) {
+ log_ndisc("Option lacks header, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ type = p[0];
+ length = p[1] * 8;
+
+ if (length == 0) {
+ log_ndisc("Zero-length option, ignoring datagram.");
+ return -EBADMSG;
+ }
+ if (left < length) {
+ log_ndisc("Option truncated, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ switch (type) {
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION:
+
+ if (length != 4*8) {
+ log_ndisc("Prefix option of invalid size, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ if (p[2] > 128) {
+ log_ndisc("Bad prefix length, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ break;
+
+ case SD_NDISC_OPTION_MTU: {
+ uint32_t m;
+
+ if (has_mtu) {
+ log_ndisc("MTU option specified twice, ignoring.");
+ continue;
+ }
+
+ if (length != 8) {
+ log_ndisc("MTU option of invalid size, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ m = be32toh(*(uint32_t*) (p + 4));
+ if (m >= IPV6_MIN_MTU) /* ignore invalidly small MTUs */
+ rt->mtu = m;
+
+ has_mtu = true;
+ break;
+ }
+
+ case SD_NDISC_OPTION_ROUTE_INFORMATION:
+ if (length < 1*8 || length > 3*8) {
+ log_ndisc("Route information option of invalid size, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ if (p[2] > 128) {
+ log_ndisc("Bad route prefix length, ignoring datagram.");
+ return -EBADMSG;
+ }
+
+ break;
+
+ case SD_NDISC_OPTION_RDNSS:
+ if (length < 3*8 || (length % (2*8)) != 1*8) {
+ log_ndisc("RDNSS option has invalid size.");
+ return -EBADMSG;
+ }
+
+ break;
+
+ case SD_NDISC_OPTION_FLAGS_EXTENSION:
+
+ if (has_flag_extension) {
+ log_ndisc("Flags extension option specified twice, ignoring.");
+ continue;
+ }
+
+ if (length < 1*8) {
+ log_ndisc("Flags extension option has invalid size.");
+ return -EBADMSG;
+ }
+
+ /* Add in the additional flags bits */
+ rt->flags |=
+ ((uint64_t) p[2] << 8) |
+ ((uint64_t) p[3] << 16) |
+ ((uint64_t) p[4] << 24) |
+ ((uint64_t) p[5] << 32) |
+ ((uint64_t) p[6] << 40) |
+ ((uint64_t) p[7] << 48);
+
+ has_flag_extension = true;
+ break;
+
+ case SD_NDISC_OPTION_DNSSL:
+ if (length < 2*8) {
+ log_ndisc("DNSSL option has invalid size.");
+ return -EBADMSG;
+ }
+
+ break;
+ }
+
+ p += length, left -= length;
+ }
+
+ rt->rindex = sizeof(struct nd_router_advert);
+ return 0;
+}
+
+_public_ int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->hop_limit;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret_flags, -EINVAL);
+
+ *ret_flags = rt->flags;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret_lifetime, -EINVAL);
+
+ *ret_lifetime = rt->lifetime;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ *ret = rt->preference;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (rt->mtu <= 0)
+ return -ENODATA;
+
+ *ret = rt->mtu;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) {
+ assert_return(rt, -EINVAL);
+
+ assert(rt->raw_size >= sizeof(struct nd_router_advert));
+ rt->rindex = sizeof(struct nd_router_advert);
+
+ return rt->rindex < rt->raw_size;
+}
+
+_public_ int sd_ndisc_router_option_next(sd_ndisc_router *rt) {
+ size_t length;
+
+ assert_return(rt, -EINVAL);
+
+ if (rt->rindex == rt->raw_size) /* EOF */
+ return -ESPIPE;
+
+ if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
+ return -EBADMSG;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (rt->rindex + length > rt->raw_size)
+ return -EBADMSG;
+
+ rt->rindex += length;
+ return rt->rindex < rt->raw_size;
+}
+
+_public_ int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) {
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (rt->rindex == rt->raw_size) /* EOF */
+ return -ESPIPE;
+
+ if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
+ return -EBADMSG;
+
+ *ret = NDISC_ROUTER_OPTION_TYPE(rt);
+ return 0;
+}
+
+_public_ int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) {
+ uint8_t k;
+ int r;
+
+ assert_return(rt, -EINVAL);
+
+ r = sd_ndisc_router_option_get_type(rt, &k);
+ if (r < 0)
+ return r;
+
+ return type == k;
+}
+
+_public_ int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) {
+ size_t length;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ /* Note that this returns the full option, including the option header */
+
+ if (rt->rindex + 2 > rt->raw_size)
+ return -EBADMSG;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (rt->rindex + length > rt->raw_size)
+ return -EBADMSG;
+
+ *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
+ *size = length;
+
+ return 0;
+}
+
+static int get_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix_info **ret) {
+ struct nd_opt_prefix_info *ri;
+ size_t length;
+ int r;
+
+ assert(rt);
+ assert(ret);
+
+ r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREFIX_INFORMATION);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EMEDIUMTYPE;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (length != sizeof(struct nd_opt_prefix_info))
+ return -EBADMSG;
+
+ ri = (struct nd_opt_prefix_info*) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex);
+ if (ri->nd_opt_pi_prefix_len > 128)
+ return -EBADMSG;
+
+ *ret = ri;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
+ struct nd_opt_prefix_info *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_prefix_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = be32toh(ri->nd_opt_pi_valid_time);
+ return 0;
+}
+
+_public_ int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
+ struct nd_opt_prefix_info *pi;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ *ret = be32toh(pi->nd_opt_pi_preferred_time);
+ return 0;
+}
+
+_public_ int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret) {
+ struct nd_opt_prefix_info *pi;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ *ret = pi->nd_opt_pi_flags_reserved;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
+ struct nd_opt_prefix_info *pi;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret_addr, -EINVAL);
+
+ r = get_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ *ret_addr = pi->nd_opt_pi_prefix;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
+ struct nd_opt_prefix_info *pi;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_prefix_info(rt, &pi);
+ if (r < 0)
+ return r;
+
+ if (pi->nd_opt_pi_prefix_len > 128)
+ return -EBADMSG;
+
+ *ret = pi->nd_opt_pi_prefix_len;
+ return 0;
+}
+
+static int get_route_info(sd_ndisc_router *rt, uint8_t **ret) {
+ uint8_t *ri;
+ size_t length;
+ int r;
+
+ assert(rt);
+ assert(ret);
+
+ r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_ROUTE_INFORMATION);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EMEDIUMTYPE;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (length < 1*8 || length > 3*8)
+ return -EBADMSG;
+
+ ri = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
+
+ if (ri[2] > 128)
+ return -EBADMSG;
+
+ *ret = ri;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_route_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = be32toh(*(uint32_t*) (ri + 4));
+ return 0;
+}
+
+_public_ int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret_addr, -EINVAL);
+
+ r = get_route_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ zero(*ret_addr);
+ memcpy(ret_addr, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8);
+
+ return 0;
+}
+
+_public_ int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_route_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = ri[2];
+ return 0;
+}
+
+_public_ int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_route_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = (ri[3] >> 3) & 3;
+ if (!IN_SET(*ret, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
+ *ret = SD_NDISC_PREFERENCE_MEDIUM;
+
+ return 0;
+}
+
+static int get_rdnss_info(sd_ndisc_router *rt, uint8_t **ret) {
+ size_t length;
+ int r;
+
+ assert(rt);
+ assert(ret);
+
+ r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EMEDIUMTYPE;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (length < 3*8 || (length % (2*8)) != 1*8)
+ return -EBADMSG;
+
+ *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_rdnss_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = (const struct in6_addr*) (ri + 8);
+ return (NDISC_ROUTER_OPTION_LENGTH(rt) - 8) / 16;
+}
+
+_public_ int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_rdnss_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret = be32toh(*(uint32_t*) (ri + 4));
+ return 0;
+}
+
+static int get_dnssl_info(sd_ndisc_router *rt, uint8_t **ret) {
+ size_t length;
+ int r;
+
+ assert(rt);
+ assert(ret);
+
+ r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_DNSSL);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EMEDIUMTYPE;
+
+ length = NDISC_ROUTER_OPTION_LENGTH(rt);
+ if (length < 2*8)
+ return -EBADMSG;
+
+ *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
+ return 0;
+}
+
+_public_ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) {
+ _cleanup_strv_free_ char **l = NULL;
+ _cleanup_free_ char *e = NULL;
+ size_t allocated = 0, n = 0, left;
+ uint8_t *ri, *p;
+ bool first = true;
+ int r;
+ unsigned k = 0;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ r = get_dnssl_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ p = ri + 8;
+ left = NDISC_ROUTER_OPTION_LENGTH(rt) - 8;
+
+ for (;;) {
+ if (left == 0) {
+
+ if (n > 0) /* Not properly NUL terminated */
+ return -EBADMSG;
+
+ break;
+ }
+
+ if (*p == 0) {
+ /* Found NUL termination */
+
+ if (n > 0) {
+ _cleanup_free_ char *normalized = NULL;
+
+ e[n] = 0;
+ r = dns_name_normalize(e, &normalized);
+ if (r < 0)
+ return r;
+
+ /* Ignore the root domain name or "localhost" and friends */
+ if (!is_localhost(normalized) &&
+ !dns_name_is_root(normalized)) {
+
+ if (strv_push(&l, normalized) < 0)
+ return -ENOMEM;
+
+ normalized = NULL;
+ k++;
+ }
+ }
+
+ n = 0;
+ first = true;
+ p++, left--;
+ continue;
+ }
+
+ /* Check for compression (which is not allowed) */
+ if (*p > 63)
+ return -EBADMSG;
+
+ if (1U + *p + 1U > left)
+ return -EBADMSG;
+
+ if (!GREEDY_REALLOC(e, allocated, n + !first + DNS_LABEL_ESCAPED_MAX + 1U))
+ return -ENOMEM;
+
+ if (first)
+ first = false;
+ else
+ e[n++] = '.';
+
+ r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ n += r;
+
+ left -= 1 + *p;
+ p += 1 + *p;
+ }
+
+ if (strv_isempty(l)) {
+ *ret = NULL;
+ return 0;
+ }
+
+ *ret = l;
+ l = NULL;
+
+ return k;
+}
+
+_public_ int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret_sec) {
+ uint8_t *ri;
+ int r;
+
+ assert_return(rt, -EINVAL);
+ assert_return(ret_sec, -EINVAL);
+
+ r = get_dnssl_info(rt, &ri);
+ if (r < 0)
+ return r;
+
+ *ret_sec = be32toh(*(uint32_t*) (ri + 4));
+ return 0;
+}
diff --git a/src/libsystemd-network/ndisc-router.h b/src/libsystemd-network/ndisc-router.h
new file mode 100644
index 0000000000..1fe703da63
--- /dev/null
+++ b/src/libsystemd-network/ndisc-router.h
@@ -0,0 +1,62 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "sd-ndisc.h"
+
+#include "time-util.h"
+
+struct sd_ndisc_router {
+ unsigned n_ref;
+
+ triple_timestamp timestamp;
+ struct in6_addr address;
+
+ /* The raw packet size. The data is appended to the object, accessible via NDIS_ROUTER_RAW() */
+ size_t raw_size;
+
+ /* The current read index for the iterative option interface */
+ size_t rindex;
+
+ uint64_t flags;
+ unsigned preference;
+ uint16_t lifetime;
+
+ uint8_t hop_limit;
+ uint32_t mtu;
+};
+
+static inline void* NDISC_ROUTER_RAW(const sd_ndisc_router *rt) {
+ return (uint8_t*) rt + ALIGN(sizeof(sd_ndisc_router));
+}
+
+static inline void *NDISC_ROUTER_OPTION_DATA(const sd_ndisc_router *rt) {
+ return ((uint8_t*) NDISC_ROUTER_RAW(rt)) + rt->rindex;
+}
+
+static inline uint8_t NDISC_ROUTER_OPTION_TYPE(const sd_ndisc_router *rt) {
+ return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[0];
+}
+static inline size_t NDISC_ROUTER_OPTION_LENGTH(const sd_ndisc_router *rt) {
+ return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[1] * 8;
+}
+
+sd_ndisc_router *ndisc_router_new(size_t raw_size);
+int ndisc_router_parse(sd_ndisc_router *rt);
diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c
index bfaa75880b..9d78b953fc 100644
--- a/src/libsystemd-network/network-internal.c
+++ b/src/libsystemd-network/network-internal.c
@@ -102,16 +102,16 @@ bool net_match_config(const struct ether_addr *match_mac,
const char *dev_type,
const char *dev_name) {
- if (match_host && !condition_test(match_host))
+ if (match_host && condition_test(match_host) <= 0)
return false;
- if (match_virt && !condition_test(match_virt))
+ if (match_virt && condition_test(match_virt) <= 0)
return false;
- if (match_kernel && !condition_test(match_kernel))
+ if (match_kernel && condition_test(match_kernel) <= 0)
return false;
- if (match_arch && !condition_test(match_arch))
+ if (match_arch && condition_test(match_arch) <= 0)
return false;
if (match_mac && (!dev_mac || memcmp(match_mac, dev_mac, ETH_ALEN)))
@@ -380,18 +380,21 @@ int deserialize_in_addrs(struct in_addr **ret, const char *string) {
return size;
}
-void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses,
- size_t size) {
+void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size) {
unsigned i;
assert(f);
assert(addresses);
assert(size);
- for (i = 0; i < size; i++)
- fprintf(f, SD_NDISC_ADDRESS_FORMAT_STR"%s",
- SD_NDISC_ADDRESS_FORMAT_VAL(addresses[i]),
- (i < (size - 1)) ? " ": "");
+ for (i = 0; i < size; i++) {
+ char buffer[INET6_ADDRSTRLEN];
+
+ fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f);
+
+ if (i < size - 1)
+ fputc(' ', f);
+ }
}
int deserialize_in6_addrs(struct in6_addr **ret, const char *string) {
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index ea4f03df1d..11ee2e252e 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -260,7 +260,7 @@ static int dhcp_server_send_unicast_raw(sd_dhcp_server *server,
DHCPPacket *packet, size_t len) {
union sockaddr_union link = {
.ll.sll_family = AF_PACKET,
- .ll.sll_protocol = htons(ETH_P_IP),
+ .ll.sll_protocol = htobe16(ETH_P_IP),
.ll.sll_ifindex = server->ifindex,
.ll.sll_halen = ETH_ALEN,
};
diff --git a/src/libsystemd-network/sd-lldp.c b/src/libsystemd-network/sd-lldp.c
index 9d4587c80e..0bd1e66aa0 100644
--- a/src/libsystemd-network/sd-lldp.c
+++ b/src/libsystemd-network/sd-lldp.c
@@ -43,7 +43,6 @@ static void lldp_flush_neighbors(sd_lldp *lldp) {
static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) {
assert(lldp);
- assert(n);
log_lldp("Invoking callback for '%c'.", event);
@@ -138,6 +137,7 @@ static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
if (lldp_neighbor_equal(n, old)) {
/* Is this equal, then restart the TTL counter, but don't do anyting else. */
+ old->timestamp = n->timestamp;
lldp_start_timer(lldp, old);
lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
return 0;
@@ -171,7 +171,7 @@ static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
finish:
if (old)
- lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n);
+ lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
return r;
}
@@ -202,6 +202,7 @@ static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, v
_cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
ssize_t space, length;
sd_lldp *lldp = userdata;
+ struct timespec ts;
assert(fd >= 0);
assert(lldp);
@@ -215,21 +216,41 @@ static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, v
return -ENOMEM;
length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
- if (length < 0)
+ if (length < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+
return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
+ }
if ((size_t) length != n->raw_size) {
log_lldp("Packet size mismatch.");
return -EINVAL;
}
+ /* Try to get the timestamp of this packet if it is known */
+ if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
+ triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
+ else
+ triple_timestamp_get(&n->timestamp);
+
return lldp_handle_datagram(lldp, n);
}
+static void lldp_reset(sd_lldp *lldp) {
+ assert(lldp);
+
+ lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
+ lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
+ lldp->fd = safe_close(lldp->fd);
+}
+
_public_ int sd_lldp_start(sd_lldp *lldp) {
int r;
assert_return(lldp, -EINVAL);
+ assert_return(lldp->event, -EINVAL);
+ assert_return(lldp->ifindex > 0, -EINVAL);
if (lldp->fd >= 0)
return 0;
@@ -240,24 +261,21 @@ _public_ int sd_lldp_start(sd_lldp *lldp) {
if (lldp->fd < 0)
return lldp->fd;
- if (lldp->event) {
- r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
- if (r < 0)
- goto fail;
+ r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
+ if (r < 0)
+ goto fail;
- r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
- if (r < 0)
- goto fail;
+ r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
+ if (r < 0)
+ goto fail;
- (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
- }
+ (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
+ log_lldp("Started LLDP client");
return 1;
fail:
- lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
- lldp->fd = safe_close(lldp->fd);
-
+ lldp_reset(lldp);
return r;
}
@@ -267,10 +285,9 @@ _public_ int sd_lldp_stop(sd_lldp *lldp) {
if (lldp->fd < 0)
return 0;
- lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
- lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
- lldp->fd = safe_close(lldp->fd);
+ log_lldp("Stopping LLDP client");
+ lldp_reset(lldp);
lldp_flush_neighbors(lldp);
return 1;
@@ -305,6 +322,12 @@ _public_ int sd_lldp_detach_event(sd_lldp *lldp) {
return 0;
}
+_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) {
+ assert_return(lldp, NULL);
+
+ return lldp->event;
+}
+
_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
assert_return(lldp, -EINVAL);
@@ -314,39 +337,60 @@ _public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *us
return 0;
}
+_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) {
+ assert_return(lldp, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+ assert_return(lldp->fd < 0, -EBUSY);
+
+ lldp->ifindex = ifindex;
+ return 0;
+}
+
+_public_ sd_lldp* sd_lldp_ref(sd_lldp *lldp) {
+
+ if (!lldp)
+ return NULL;
+
+ assert(lldp->n_ref > 0);
+ lldp->n_ref++;
+
+ return lldp;
+}
+
_public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) {
if (!lldp)
return NULL;
+ assert(lldp->n_ref > 0);
+ lldp->n_ref --;
+
+ if (lldp->n_ref > 0)
+ return NULL;
+
+ lldp_reset(lldp);
+ sd_lldp_detach_event(lldp);
lldp_flush_neighbors(lldp);
hashmap_free(lldp->neighbor_by_id);
prioq_free(lldp->neighbor_by_expiry);
-
- sd_event_source_unref(lldp->io_event_source);
- sd_event_source_unref(lldp->timer_event_source);
- sd_event_unref(lldp->event);
- safe_close(lldp->fd);
-
free(lldp);
return NULL;
}
-_public_ int sd_lldp_new(sd_lldp **ret, int ifindex) {
+_public_ int sd_lldp_new(sd_lldp **ret) {
_cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
int r;
assert_return(ret, -EINVAL);
- assert_return(ifindex > 0, -EINVAL);
lldp = new0(sd_lldp, 1);
if (!lldp)
return -ENOMEM;
+ lldp->n_ref = 1;
lldp->fd = -1;
- lldp->ifindex = ifindex;
lldp->neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX;
lldp->capability_mask = (uint16_t) -1;
@@ -486,11 +530,10 @@ _public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *
/* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
* that our own can be filtered out here. */
- if (!addr) {
+ if (addr)
+ lldp->filter_address = *addr;
+ else
zero(lldp->filter_address);
- return 0;
- }
- lldp->filter_address = *addr;
return 0;
}
diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c
index ccb8002173..07b0d7f704 100644
--- a/src/libsystemd-network/sd-ndisc.c
+++ b/src/libsystemd-network/sd-ndisc.c
@@ -19,165 +19,71 @@
#include <netinet/icmp6.h>
#include <netinet/in.h>
-#include <netinet/ip6.h>
-#include <stdbool.h>
-#include <string.h>
-#include <sys/ioctl.h>
#include "sd-ndisc.h"
#include "alloc-util.h"
-#include "async.h"
+#include "fd-util.h"
#include "icmp6-util.h"
#include "in-addr-util.h"
-#include "list.h"
+#include "ndisc-internal.h"
+#include "ndisc-router.h"
#include "socket-util.h"
#include "string-util.h"
+#include "util.h"
#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
-#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
+#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
-enum NDiscState {
- NDISC_STATE_IDLE,
- NDISC_STATE_SOLICITATION_SENT,
- NDISC_STATE_ADVERTISEMENT_LISTEN,
- _NDISC_STATE_MAX,
- _NDISC_STATE_INVALID = -1,
-};
+static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) {
+ assert(ndisc);
-#define IP6_MIN_MTU 1280U
-#define ICMP6_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
-#define NDISC_OPT_LEN_UNITS 8U
+ log_ndisc("Invoking callback for '%c'.", event);
-#define ND_RA_FLAG_PREF 0x18
-#define ND_RA_FLAG_PREF_LOW 0x03
-#define ND_RA_FLAG_PREF_MEDIUM 0x0
-#define ND_RA_FLAG_PREF_HIGH 0x1
-#define ND_RA_FLAG_PREF_INVALID 0x2
+ if (!ndisc->callback)
+ return;
-typedef struct NDiscPrefix NDiscPrefix;
-
-struct NDiscPrefix {
- unsigned n_ref;
-
- sd_ndisc *nd;
-
- LIST_FIELDS(NDiscPrefix, prefixes);
-
- uint8_t len;
- usec_t valid_until;
- struct in6_addr addr;
-};
-
-struct sd_ndisc {
- unsigned n_ref;
-
- enum NDiscState state;
- int ifindex;
- int fd;
-
- sd_event *event;
- int event_priority;
-
- struct ether_addr mac_addr;
- uint32_t mtu;
-
- LIST_HEAD(NDiscPrefix, prefixes);
-
- sd_event_source *recv_event_source;
- sd_event_source *timeout_event_source;
-
- unsigned nd_sent;
-
- sd_ndisc_router_callback_t router_callback;
- sd_ndisc_prefix_autonomous_callback_t prefix_autonomous_callback;
- sd_ndisc_prefix_onlink_callback_t prefix_onlink_callback;
- sd_ndisc_callback_t callback;
- void *userdata;
-};
-
-#define log_ndisc_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "NDisc CLIENT: " fmt, ##__VA_ARGS__)
-#define log_ndisc(p, fmt, ...) log_ndisc_errno(p, 0, fmt, ##__VA_ARGS__)
-
-static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) {
-
- if (!prefix)
- return NULL;
-
- assert(prefix->n_ref > 0);
- prefix->n_ref--;
-
- if (prefix->n_ref > 0)
- return NULL;
-
- if (prefix->nd)
- LIST_REMOVE(prefixes, prefix->nd->prefixes, prefix);
-
- free(prefix);
-
- return NULL;
-}
-
-static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) {
- NDiscPrefix *prefix;
-
- assert(ret);
-
- prefix = new0(NDiscPrefix, 1);
- if (!prefix)
- return -ENOMEM;
-
- prefix->n_ref = 1;
- LIST_INIT(prefixes, prefix);
- prefix->nd = nd;
-
- *ret = prefix;
- return 0;
+ ndisc->callback(ndisc, event, rt, ndisc->userdata);
}
-int sd_ndisc_set_callback(
+_public_ int sd_ndisc_set_callback(
sd_ndisc *nd,
- sd_ndisc_router_callback_t router_callback,
- sd_ndisc_prefix_onlink_callback_t prefix_onlink_callback,
- sd_ndisc_prefix_autonomous_callback_t prefix_autonomous_callback,
sd_ndisc_callback_t callback,
void *userdata) {
assert_return(nd, -EINVAL);
- nd->router_callback = router_callback;
- nd->prefix_onlink_callback = prefix_onlink_callback;
- nd->prefix_autonomous_callback = prefix_autonomous_callback;
nd->callback = callback;
nd->userdata = userdata;
return 0;
}
-int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
+_public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
assert_return(nd, -EINVAL);
assert_return(ifindex > 0, -EINVAL);
+ assert_return(nd->fd < 0, -EBUSY);
nd->ifindex = ifindex;
return 0;
}
-int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
+_public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
assert_return(nd, -EINVAL);
if (mac_addr)
- memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
+ nd->mac_addr = *mac_addr;
else
zero(nd->mac_addr);
return 0;
-
}
-int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
+_public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
int r;
assert_return(nd, -EINVAL);
+ assert_return(nd->fd < 0, -EBUSY);
assert_return(!nd->event, -EBUSY);
if (event)
@@ -193,21 +99,22 @@ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
return 0;
}
-int sd_ndisc_detach_event(sd_ndisc *nd) {
+_public_ int sd_ndisc_detach_event(sd_ndisc *nd) {
+
assert_return(nd, -EINVAL);
+ assert_return(nd->fd < 0, -EBUSY);
nd->event = sd_event_unref(nd->event);
-
return 0;
}
-sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
+_public_ sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
assert_return(nd, NULL);
return nd->event;
}
-sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
+_public_ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
if (!nd)
return NULL;
@@ -221,15 +128,14 @@ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
static int ndisc_reset(sd_ndisc *nd) {
assert(nd);
- nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
- nd->fd = asynchronous_close(nd->fd);
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
+ nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
+ nd->fd = safe_close(nd->fd);
return 0;
}
-sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
- NDiscPrefix *prefix, *p;
+_public_ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
if (!nd)
return NULL;
@@ -242,16 +148,12 @@ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
ndisc_reset(nd);
sd_ndisc_detach_event(nd);
-
- LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes)
- prefix = ndisc_prefix_unref(prefix);
-
free(nd);
return NULL;
}
-int sd_ndisc_new(sd_ndisc **ret) {
+_public_ int sd_ndisc_new(sd_ndisc **ret) {
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
assert_return(ret, -EINVAL);
@@ -261,223 +163,70 @@ int sd_ndisc_new(sd_ndisc **ret) {
return -ENOMEM;
nd->n_ref = 1;
- nd->ifindex = -1;
nd->fd = -1;
- LIST_HEAD_INIT(nd->prefixes);
-
*ret = nd;
nd = NULL;
return 0;
}
-int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
+_public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
assert_return(nd, -EINVAL);
assert_return(mtu, -EINVAL);
if (nd->mtu == 0)
- return -ENOMSG;
+ return -ENODATA;
*mtu = nd->mtu;
return 0;
}
-static int prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
- const struct in6_addr *addr,
- uint8_t addr_prefixlen) {
- uint8_t bytes, mask, len;
-
- assert(prefix);
- assert(addr);
-
- len = MIN(prefixlen, addr_prefixlen);
-
- bytes = len / 8;
- mask = 0xff << (8 - len % 8);
+_public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) {
+ assert_return(nd, -EINVAL);
+ assert_return(ret, -EINVAL);
- if (memcmp(prefix, addr, bytes) != 0 ||
- (prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask))
- return -EADDRNOTAVAIL;
+ if (nd->hop_limit == 0)
+ return -ENODATA;
+ *ret = nd->hop_limit;
return 0;
}
-static int ndisc_prefix_match(sd_ndisc *nd, const struct in6_addr *addr,
- uint8_t addr_len, NDiscPrefix **result) {
- NDiscPrefix *prefix, *p;
- usec_t time_now;
- int r;
-
- assert(nd);
-
- r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
- if (r < 0)
- return r;
-
- LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
- if (prefix->valid_until < time_now) {
- prefix = ndisc_prefix_unref(prefix);
- continue;
- }
-
- if (prefix_match(&prefix->addr, prefix->len, addr, addr_len) >= 0) {
- *result = prefix;
- return 0;
- }
- }
-
- return -EADDRNOTAVAIL;
-}
-
-static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
- const struct nd_opt_prefix_info *prefix_opt) {
- NDiscPrefix *prefix;
- uint32_t lifetime_valid, lifetime_preferred;
- usec_t time_now;
- char time_string[FORMAT_TIMESPAN_MAX];
+static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
int r;
assert(nd);
- assert(prefix_opt);
+ assert(rt);
- if (len < prefix_opt->nd_opt_pi_len)
- return -EBADMSG;
-
- if (!(prefix_opt->nd_opt_pi_flags_reserved & (ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO)))
+ r = ndisc_router_parse(rt);
+ if (r == -EBADMSG) /* Bad packet */
return 0;
-
- if (in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &prefix_opt->nd_opt_pi_prefix) > 0)
- return 0;
-
- lifetime_valid = be32toh(prefix_opt->nd_opt_pi_valid_time);
- lifetime_preferred = be32toh(prefix_opt->nd_opt_pi_preferred_time);
-
- if (lifetime_valid < lifetime_preferred)
- return 0;
-
- r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix,
- prefix_opt->nd_opt_pi_prefix_len, &prefix);
- if (r < 0) {
- if (r != -EADDRNOTAVAIL)
- return r;
-
- /* if router advertisement prefix valid timeout is zero, the timeout
- callback will be called immediately to clean up the prefix */
-
- r = ndisc_prefix_new(nd, &prefix);
- if (r < 0)
- return r;
-
- prefix->len = prefix_opt->nd_opt_pi_prefix_len;
-
- memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
- sizeof(prefix->addr));
-
- log_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
- SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
- prefix->len, lifetime_valid,
- format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
-
- LIST_PREPEND(prefixes, nd->prefixes, prefix);
-
- } else {
- if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
- uint8_t prefixlen;
-
- prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
-
- log_ndisc(nd, "Prefix length mismatch %d/%d using %d",
- prefix->len,
- prefix_opt->nd_opt_pi_prefix_len,
- prefixlen);
-
- prefix->len = prefixlen;
- }
-
- log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
- SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
- prefix->len, lifetime_valid,
- format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
- }
-
- r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
if (r < 0)
- return r;
-
- prefix->valid_until = time_now + lifetime_valid * USEC_PER_SEC;
-
- if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) && nd->prefix_onlink_callback)
- nd->prefix_onlink_callback(nd, &prefix->addr, prefix->len, prefix->valid_until, nd->userdata);
-
- if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) && nd->prefix_autonomous_callback)
- nd->prefix_autonomous_callback(nd, &prefix->addr, prefix->len, lifetime_preferred, lifetime_valid,
- nd->userdata);
-
- return 0;
-}
-
-static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, size_t len) {
- struct nd_opt_hdr *opt_hdr;
- void *opt;
-
- assert(nd);
- assert(ra);
-
- if (len < sizeof(struct nd_router_advert) + NDISC_OPT_LEN_UNITS) {
- log_ndisc(nd, "Router Advertisement below minimum length");
- return -EBADMSG;
- }
-
- len -= sizeof(struct nd_router_advert);
- opt = ra + 1;
- opt_hdr = opt;
-
- while (len != 0 && len >= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS) {
- struct nd_opt_mtu *opt_mtu;
- struct nd_opt_prefix_info *opt_prefix;
- uint32_t mtu;
-
- if (opt_hdr->nd_opt_len == 0)
- return -EBADMSG;
-
- switch (opt_hdr->nd_opt_type) {
-
- case ND_OPT_MTU:
- opt_mtu = opt;
-
- mtu = be32toh(opt_mtu->nd_opt_mtu_mtu);
-
- if (mtu != nd->mtu) {
- nd->mtu = MAX(mtu, IP6_MIN_MTU);
- log_ndisc(nd, "Router Advertisement link MTU %d using %d", mtu, nd->mtu);
- }
-
- break;
-
- case ND_OPT_PREFIX_INFORMATION:
- opt_prefix = opt;
- ndisc_prefix_update(nd, len, opt_prefix);
- break;
- }
+ return 0;
- len -= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS;
- opt = (void*) ((uint8_t*) opt + opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS);
- opt_hdr = opt;
- }
+ /* Update global variables we keep */
+ if (rt->mtu > 0)
+ nd->mtu = rt->mtu;
+ if (rt->hop_limit > 0)
+ nd->hop_limit = rt->hop_limit;
- if (len > 0)
- log_ndisc(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
+ log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec",
+ rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none",
+ rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium",
+ rt->lifetime);
+ ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
return 0;
}
-static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- _cleanup_free_ struct nd_router_advert *ra = NULL;
+static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
sd_ndisc *nd = userdata;
union {
struct cmsghdr cmsghdr;
- uint8_t buf[CMSG_LEN(sizeof(int))];
+ uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */
+ CMSG_SPACE(sizeof(struct timeval))];
} control = {};
struct iovec iov = {};
union sockaddr_union sa = {};
@@ -490,10 +239,7 @@ static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t
.msg_controllen = sizeof(control),
};
struct cmsghdr *cmsg;
- struct in6_addr *gw;
- unsigned lifetime;
ssize_t len, buflen;
- int r, pref, stateful;
assert(s);
assert(nd);
@@ -501,35 +247,47 @@ static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t
buflen = next_datagram_size_fd(fd);
if (buflen < 0)
- return buflen;
-
- iov.iov_len = buflen;
+ return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m");
- ra = malloc(iov.iov_len);
- if (!ra)
+ rt = ndisc_router_new(buflen);
+ if (!rt)
return -ENOMEM;
- iov.iov_base = ra;
+ iov.iov_base = NDISC_ROUTER_RAW(rt);
+ iov.iov_len = rt->raw_size;
- len = recvmsg(fd, &msg, 0);
+ len = recvmsg(fd, &msg, MSG_DONTWAIT);
if (len < 0) {
if (errno == EAGAIN || errno == EINTR)
return 0;
- return log_ndisc_errno(nd, errno, "Could not receive message from ICMPv6 socket: %m");
+ return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m");
}
- if ((size_t) len < sizeof(struct nd_router_advert)) {
- log_ndisc(nd, "Too small to be a router advertisement: ignoring");
- return 0;
+
+ if ((size_t) len != rt->raw_size) {
+ log_ndisc("Packet size mismatch.");
+ return -EINVAL;
}
- if (msg.msg_namelen == 0)
- gw = NULL; /* only happens when running the test-suite over a socketpair */
- else if (msg.msg_namelen != sizeof(sa.in6)) {
- log_ndisc(nd, "Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t)msg.msg_namelen);
- return 0;
- } else
- gw = &sa.in6.sin6_addr;
+ if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
+ sa.in6.sin6_family == AF_INET6) {
+
+ if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr) <= 0) {
+ _cleanup_free_ char *addr = NULL;
+
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr, &addr);
+ log_ndisc("Received RA from non-link-local address %s. Ignoring.", strna(addr));
+ return 0;
+ }
+
+ rt->address = sa.in6.sin6_addr;
+
+ } else if (msg.msg_namelen > 0) {
+ log_ndisc("Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t) msg.msg_namelen);
+ return -EINVAL;
+ }
+
+ /* namelen == 0 only happens when running the test-suite over a socketpair */
assert(!(msg.msg_flags & MSG_CTRUNC));
assert(!(msg.msg_flags & MSG_TRUNC));
@@ -538,61 +296,29 @@ static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t
if (cmsg->cmsg_level == SOL_IPV6 &&
cmsg->cmsg_type == IPV6_HOPLIMIT &&
cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
- int hops = *(int*)CMSG_DATA(cmsg);
+ int hops = *(int*) CMSG_DATA(cmsg);
if (hops != 255) {
- log_ndisc(nd, "Received RA with invalid hop limit %d. Ignoring.", hops);
+ log_ndisc("Received RA with invalid hop limit %d. Ignoring.", hops);
return 0;
}
-
- break;
}
- }
- if (gw && !in_addr_is_link_local(AF_INET6, (const union in_addr_union*) gw)) {
- _cleanup_free_ char *addr = NULL;
-
- (void)in_addr_to_string(AF_INET6, (const union in_addr_union*) gw, &addr);
-
- log_ndisc(nd, "Received RA from non-link-local address %s. Ignoring.", strna(addr));
- return 0;
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SO_TIMESTAMP &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
+ triple_timestamp_from_realtime(&rt->timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
}
- if (ra->nd_ra_type != ND_ROUTER_ADVERT)
- return 0;
-
- if (ra->nd_ra_code != 0)
- return 0;
+ if (!triple_timestamp_is_set(&rt->timestamp))
+ triple_timestamp_get(&rt->timestamp);
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
- nd->state = NDISC_STATE_ADVERTISEMENT_LISTEN;
-
- stateful = ra->nd_ra_flags_reserved & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER);
- pref = (ra->nd_ra_flags_reserved & ND_RA_FLAG_PREF) >> 3;
-
- if (!IN_SET(pref, ND_RA_FLAG_PREF_LOW, ND_RA_FLAG_PREF_HIGH))
- pref = ND_RA_FLAG_PREF_MEDIUM;
-
- lifetime = be16toh(ra->nd_ra_router_lifetime);
-
- log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %u sec",
- stateful & ND_RA_FLAG_MANAGED ? "MANAGED" : stateful & ND_RA_FLAG_OTHER ? "OTHER" : "none",
- pref == ND_RA_FLAG_PREF_HIGH ? "high" : pref == ND_RA_FLAG_PREF_LOW ? "low" : "medium",
- lifetime);
- r = ndisc_ra_parse(nd, ra, (size_t) len);
- if (r < 0) {
- log_ndisc_errno(nd, r, "Could not parse Router Advertisement: %m");
- return 0;
- }
-
- if (nd->router_callback)
- nd->router_callback(nd, stateful, gw, lifetime, pref, nd->userdata);
-
- return 0;
+ return ndisc_handle_datagram(nd, rt);
}
-static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
sd_ndisc *nd = userdata;
usec_t time_now, next_timeout;
int r;
@@ -601,43 +327,34 @@ static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec,
assert(nd);
assert(nd->event);
- nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
-
if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
- if (nd->callback)
- nd->callback(nd, SD_NDISC_EVENT_TIMEOUT, nd->userdata);
- nd->state = NDISC_STATE_ADVERTISEMENT_LISTEN;
- } else {
- r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
- if (r < 0) {
- log_ndisc_errno(nd, r, "Error sending Router Solicitation: %m");
- goto fail;
- } else {
- nd->state = NDISC_STATE_SOLICITATION_SENT;
- log_ndisc(nd, "Sent Router Solicitation");
- }
-
- nd->nd_sent++;
+ nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
+ ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
+ return 0;
+ }
- assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
+ r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
+ if (r < 0) {
+ log_ndisc_errno(r, "Error sending Router Solicitation: %m");
+ goto fail;
+ }
- next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
+ log_ndisc("Sent Router Solicitation");
+ nd->nd_sent++;
- r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(),
- next_timeout, 0,
- ndisc_router_solicitation_timeout, nd);
- if (r < 0) {
- log_ndisc_errno(nd, r, "Failed to allocate timer event: %m");
- goto fail;
- }
+ assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
+ next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
- r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
- if (r < 0) {
- log_ndisc_errno(nd, r, "Cannot set timer priority: %m");
- goto fail;
- }
+ r = sd_event_source_set_time(nd->timeout_event_source, next_timeout);
+ if (r < 0) {
+ log_ndisc_errno(r, "Error updating timer: %m");
+ goto fail;
+ }
- (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
+ r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT);
+ if (r < 0) {
+ log_ndisc_errno(r, "Error reenabling timer: %m");
+ goto fail;
}
return 0;
@@ -647,38 +364,36 @@ fail:
return 0;
}
-int sd_ndisc_stop(sd_ndisc *nd) {
+_public_ int sd_ndisc_stop(sd_ndisc *nd) {
assert_return(nd, -EINVAL);
- if (nd->state == NDISC_STATE_IDLE)
+ if (nd->fd < 0)
return 0;
- log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
+ log_ndisc("Stopping IPv6 Router Solicitation client");
ndisc_reset(nd);
- nd->state = NDISC_STATE_IDLE;
-
- if (nd->callback)
- nd->callback(nd, SD_NDISC_EVENT_STOP, nd->userdata);
-
- return 0;
+ return 1;
}
-int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
+_public_ int sd_ndisc_start(sd_ndisc *nd) {
int r;
assert_return(nd, -EINVAL);
assert_return(nd->event, -EINVAL);
assert_return(nd->ifindex > 0, -EINVAL);
- assert_return(nd->state == NDISC_STATE_IDLE, -EBUSY);
- r = icmp6_bind_router_solicitation(nd->ifindex);
- if (r < 0)
- return r;
+ if (nd->fd >= 0)
+ return 0;
- nd->fd = r;
+ assert(!nd->recv_event_source);
+ assert(!nd->timeout_event_source);
- r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_router_advertisement_recv, nd);
+ nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
+ if (nd->fd < 0)
+ return nd->fd;
+
+ r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
if (r < 0)
goto fail;
@@ -688,7 +403,7 @@ int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
(void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
- r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_router_solicitation_timeout, nd);
+ r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout, nd);
if (r < 0)
goto fail;
@@ -698,8 +413,8 @@ int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
(void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
- log_ndisc(ns, "Started IPv6 Router Solicitation client");
- return 0;
+ log_ndisc("Started IPv6 Router Solicitation client");
+ return 1;
fail:
ndisc_reset(nd);
diff --git a/src/libsystemd-network/test-lldp.c b/src/libsystemd-network/test-lldp.c
index 1aae2253c0..6bcd65de0a 100644
--- a/src/libsystemd-network/test-lldp.c
+++ b/src/libsystemd-network/test-lldp.c
@@ -54,11 +54,11 @@ static void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n
static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *cb_data) {
int r;
- r = sd_lldp_new(lldp, 42);
+ r = sd_lldp_new(lldp);
if (r < 0)
return r;
- r = sd_lldp_attach_event(*lldp, e, 0);
+ r = sd_lldp_set_ifindex(*lldp, 42);
if (r < 0)
return r;
@@ -66,6 +66,10 @@ static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *
if (r < 0)
return r;
+ r = sd_lldp_attach_event(*lldp, e, 0);
+ if (r < 0)
+ return r;
+
r = sd_lldp_start(*lldp);
if (r < 0)
return r;
diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c
index 4817c968ac..d9669488be 100644
--- a/src/libsystemd-network/test-ndisc-rs.c
+++ b/src/libsystemd-network/test-ndisc-rs.c
@@ -18,11 +18,15 @@
***/
#include <netinet/icmp6.h>
+#include <arpa/inet.h>
#include "sd-ndisc.h"
+#include "alloc-util.h"
+#include "hexdecoct.h"
#include "icmp6-util.h"
#include "socket-util.h"
+#include "strv.h"
static struct ether_addr mac_addr = {
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
@@ -35,6 +39,144 @@ static int test_fd[2];
typedef int (*send_ra_t)(uint8_t flags);
static send_ra_t send_ra_function;
+static void router_dump(sd_ndisc_router *rt) {
+ struct in6_addr addr;
+ char buf[FORMAT_TIMESTAMP_MAX];
+ uint8_t hop_limit;
+ uint64_t t, flags;
+ uint32_t mtu;
+ uint16_t lifetime;
+ unsigned preference;
+ int r;
+
+ assert_se(rt);
+
+ log_info("--");
+ assert_se(sd_ndisc_router_get_address(rt, &addr) == -ENODATA);
+
+ assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
+ log_info("Timestamp: %s", format_timestamp(buf, sizeof(buf), t));
+
+ assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_MONOTONIC, &t) >= 0);
+ log_info("Monotonic: %" PRIu64, t);
+
+ if (sd_ndisc_router_get_hop_limit(rt, &hop_limit) < 0)
+ log_info("No hop limit set");
+ else
+ log_info("Hop limit: %u", hop_limit);
+
+ assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0);
+ log_info("Flags: <%s|%s>",
+ flags & ND_RA_FLAG_OTHER ? "OTHER" : "",
+ flags & ND_RA_FLAG_MANAGED ? "MANAGED" : "");
+
+ assert_se(sd_ndisc_router_get_preference(rt, &preference) >= 0);
+ log_info("Preference: %s",
+ preference == SD_NDISC_PREFERENCE_LOW ? "low" :
+ preference == SD_NDISC_PREFERENCE_HIGH ? "high" : "medium");
+
+ assert_se(sd_ndisc_router_get_lifetime(rt, &lifetime) >= 0);
+ log_info("Lifetime: %" PRIu16, lifetime);
+
+ if (sd_ndisc_router_get_mtu(rt, &mtu) < 0)
+ log_info("No MTU set");
+ else
+ log_info("MTU: %" PRIu32, mtu);
+
+ r = sd_ndisc_router_option_rewind(rt);
+ for (;;) {
+ uint8_t type;
+
+ assert_se(r >= 0);
+
+ if (r == 0)
+ break;
+
+ assert_se(sd_ndisc_router_option_get_type(rt, &type) >= 0);
+
+ log_info(">> Option %u", type);
+
+ switch (type) {
+
+ case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
+ case SD_NDISC_OPTION_TARGET_LL_ADDRESS: {
+ _cleanup_free_ char *c = NULL;
+ const void *p;
+ size_t n;
+
+ assert_se(sd_ndisc_router_option_get_raw(rt, &p, &n) >= 0);
+ assert_se(n > 2);
+ assert_se(c = hexmem((uint8_t*) p + 2, n - 2));
+
+ log_info("Address: %s", c);
+ break;
+ }
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION: {
+ uint32_t lifetime_valid, lifetime_preferred;
+ unsigned prefix_len;
+ uint8_t pfl;
+ struct in6_addr a;
+ char buff[INET6_ADDRSTRLEN];
+
+ assert_se(sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid) >= 0);
+ log_info("Valid Lifetime: %" PRIu32, lifetime_valid);
+
+ assert_se(sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred) >= 0);
+ log_info("Preferred Lifetime: %" PRIu32, lifetime_preferred);
+
+ assert_se(sd_ndisc_router_prefix_get_flags(rt, &pfl) >= 0);
+ log_info("Flags: <%s|%s>",
+ pfl & ND_OPT_PI_FLAG_ONLINK ? "ONLINK" : "",
+ pfl & ND_OPT_PI_FLAG_AUTO ? "AUTO" : "");
+
+ assert_se(sd_ndisc_router_prefix_get_prefixlen(rt, &prefix_len) >= 0);
+ log_info("Prefix Length: %u", prefix_len);
+
+ assert_se(sd_ndisc_router_prefix_get_address(rt, &a) >= 0);
+ log_info("Prefix: %s", inet_ntop(AF_INET6, &a, buff, sizeof(buff)));
+
+ break;
+ }
+
+ case SD_NDISC_OPTION_RDNSS: {
+ const struct in6_addr *a;
+ uint32_t lt;
+ int n, i;
+
+ n = sd_ndisc_router_rdnss_get_addresses(rt, &a);
+ assert_se(n > 0);
+
+ for (i = 0; i < n; i++) {
+ char buff[INET6_ADDRSTRLEN];
+ log_info("DNS: %s", inet_ntop(AF_INET6, a + i, buff, sizeof(buff)));
+ }
+
+ assert_se(sd_ndisc_router_rdnss_get_lifetime(rt, &lt) >= 0);
+ log_info("Lifetime: %" PRIu32, lt);
+ break;
+ }
+
+ case SD_NDISC_OPTION_DNSSL: {
+ _cleanup_strv_free_ char **l = NULL;
+ uint32_t lt;
+ int n, i;
+
+ n = sd_ndisc_router_dnssl_get_domains(rt, &l);
+ assert_se(n > 0);
+
+ for (i = 0; i < n; i++)
+ log_info("Domain: %s", l[i]);
+
+ assert_se(sd_ndisc_router_dnssl_get_lifetime(rt, &lt) >= 0);
+ log_info("Lifetime: %" PRIu32, lt);
+ break;
+ }}
+
+ r = sd_ndisc_router_option_next(rt);
+ }
+}
+
static int test_rs_hangcheck(sd_event_source *s, uint64_t usec,
void *userdata) {
assert_se(false);
@@ -83,32 +225,39 @@ int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
return send_ra_function(0);
}
-static void test_rs_done(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata) {
+static void test_callback(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) {
sd_event *e = userdata;
static unsigned idx = 0;
- uint8_t flags_array[] = {
+ uint64_t flags_array[] = {
0,
0,
0,
ND_RA_FLAG_OTHER,
ND_RA_FLAG_MANAGED
};
+ uint64_t flags;
uint32_t mtu;
assert_se(nd);
+ if (event != SD_NDISC_EVENT_ROUTER)
+ return;
+
+ router_dump(rt);
+
+ assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0);
assert_se(flags == flags_array[idx]);
idx++;
if (verbose)
- printf(" got event 0x%02x\n", flags);
+ printf(" got event 0x%02" PRIx64 "\n", flags);
if (idx < ELEMENTSOF(flags_array)) {
send_ra(flags_array[idx]);
return;
}
- assert_se(sd_ndisc_get_mtu(nd, &mtu) == -ENOMSG);
+ assert_se(sd_ndisc_get_mtu(nd, &mtu) == -ENODATA);
sd_event_exit(e, 0);
}
@@ -132,17 +281,17 @@ static void test_rs(void) {
assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0);
assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0);
- assert_se(sd_ndisc_set_callback(nd, test_rs_done, NULL, NULL, NULL, e) >= 0);
+ assert_se(sd_ndisc_set_callback(nd, test_callback, e) >= 0);
assert_se(sd_event_add_time(e, &test_hangcheck, clock_boottime_or_monotonic(),
time_now + 2 *USEC_PER_SEC, 0,
test_rs_hangcheck, NULL) >= 0);
assert_se(sd_ndisc_stop(nd) >= 0);
- assert_se(sd_ndisc_router_discovery_start(nd) >= 0);
+ assert_se(sd_ndisc_start(nd) >= 0);
assert_se(sd_ndisc_stop(nd) >= 0);
- assert_se(sd_ndisc_router_discovery_start(nd) >= 0);
+ assert_se(sd_ndisc_start(nd) >= 0);
sd_event_loop(e);
diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym
index 0b3a1708dc..542254295c 100644
--- a/src/libsystemd/libsystemd.sym
+++ b/src/libsystemd/libsystemd.sym
@@ -495,3 +495,8 @@ global:
sd_journal_open_directory_fd;
sd_journal_open_files_fd;
} LIBSYSTEMD_229;
+
+LIBSYSTEMD_231 {
+global:
+ sd_event_get_iteration;
+} LIBSYSTEMD_230;
diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c
index 02e3bf904c..32be3cdc38 100644
--- a/src/libsystemd/sd-bus/bus-common-errors.c
+++ b/src/libsystemd/sd-bus/bus-common-errors.c
@@ -44,6 +44,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_NO_ISOLATION, EPERM),
SD_BUS_ERROR_MAP(BUS_ERROR_SHUTTING_DOWN, ECANCELED),
SD_BUS_ERROR_MAP(BUS_ERROR_SCOPE_NOT_RUNNING, EHOSTDOWN),
+ SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_DYNAMIC_USER, ESRCH),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_MACHINE, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT),
diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h
index c8f369cb78..befb6fbfe0 100644
--- a/src/libsystemd/sd-bus/bus-common-errors.h
+++ b/src/libsystemd/sd-bus/bus-common-errors.h
@@ -40,6 +40,7 @@
#define BUS_ERROR_NO_ISOLATION "org.freedesktop.systemd1.NoIsolation"
#define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown"
#define BUS_ERROR_SCOPE_NOT_RUNNING "org.freedesktop.systemd1.ScopeNotRunning"
+#define BUS_ERROR_NO_SUCH_DYNAMIC_USER "org.freedesktop.systemd1.NoSuchDynamicUser"
#define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine"
#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage"
diff --git a/src/libsystemd/sd-bus/bus-match.c b/src/libsystemd/sd-bus/bus-match.c
index 397baf6f33..db01f21135 100644
--- a/src/libsystemd/sd-bus/bus-match.c
+++ b/src/libsystemd/sd-bus/bus-match.c
@@ -429,6 +429,9 @@ int bus_match_run(
r = bus_match_run(bus, c, m);
if (r != 0)
return r;
+
+ if (bus && bus->match_callbacks_modified)
+ return 0;
}
}
diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c
index b8958ec7bb..5cec804e32 100644
--- a/src/libsystemd/sd-bus/bus-message.c
+++ b/src/libsystemd/sd-bus/bus-message.c
@@ -181,7 +181,7 @@ static void *message_extend_fields(sd_bus_message *m, size_t align, size_t sz, b
if (!np)
goto poison;
} else {
- /* Initially, the header is allocated as part of of
+ /* Initially, the header is allocated as part of
* the sd_bus_message itself, let's replace it by
* dynamic data */
@@ -2865,7 +2865,7 @@ static int bus_message_close_header(sd_bus_message *m) {
/* The actual user data is finished now, we just complete the
variant and struct now (at least on gvariant). Remember
- this position, so that during parsing we know where to to
+ this position, so that during parsing we know where to
put the outer container end. */
m->user_body_size = m->body_size;
diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c
index f1e2a06050..cfd7753139 100644
--- a/src/libsystemd/sd-bus/bus-socket.c
+++ b/src/libsystemd/sd-bus/bus-socket.c
@@ -221,7 +221,7 @@ static int bus_socket_auth_verify_client(sd_bus *b) {
peer.bytes[i/2] = ((uint8_t) x << 4 | (uint8_t) y);
}
- if (!sd_id128_equal(b->server_id, SD_ID128_NULL) &&
+ if (!sd_id128_is_null(b->server_id) &&
!sd_id128_equal(b->server_id, peer))
return -EPERM;
diff --git a/src/libsystemd/sd-bus/busctl.c b/src/libsystemd/sd-bus/busctl.c
index bfe967bfb0..eb042e9c81 100644
--- a/src/libsystemd/sd-bus/busctl.c
+++ b/src/libsystemd/sd-bus/busctl.c
@@ -1987,7 +1987,7 @@ static int busctl_main(sd_bus *bus, int argc, char *argv[]) {
}
int main(int argc, char *argv[]) {
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ sd_bus *bus = NULL;
int r;
log_parse_environment();
@@ -2078,6 +2078,7 @@ int main(int argc, char *argv[]) {
r = busctl_main(bus, argc, argv);
finish:
+ sd_bus_flush_close_unref(bus);
pager_close();
strv_free(arg_matches);
diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c
index 4da9dbfd63..b20a7ebb4c 100644
--- a/src/libsystemd/sd-daemon/sd-daemon.c
+++ b/src/libsystemd/sd-daemon/sd-daemon.c
@@ -310,12 +310,12 @@ _public_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint
if (l < sizeof(struct sockaddr_in))
return -EINVAL;
- return htons(port) == sockaddr.in.sin_port;
+ return htobe16(port) == sockaddr.in.sin_port;
} else {
if (l < sizeof(struct sockaddr_in6))
return -EINVAL;
- return htons(port) == sockaddr.in6.sin6_port;
+ return htobe16(port) == sockaddr.in6.sin6_port;
}
}
diff --git a/src/libsystemd/sd-device/device-enumerator.c b/src/libsystemd/sd-device/device-enumerator.c
index 4a7a8b1f9e..62d03ae00d 100644
--- a/src/libsystemd/sd-device/device-enumerator.c
+++ b/src/libsystemd/sd-device/device-enumerator.c
@@ -696,17 +696,19 @@ static int enumerator_scan_devices_tag(sd_device_enumerator *enumerator, const c
static int enumerator_scan_devices_tags(sd_device_enumerator *enumerator) {
const char *tag;
Iterator i;
- int r;
+ int r = 0;
assert(enumerator);
SET_FOREACH(tag, enumerator->match_tag, i) {
- r = enumerator_scan_devices_tag(enumerator, tag);
- if (r < 0)
- return r;
+ int k;
+
+ k = enumerator_scan_devices_tag(enumerator, tag);
+ if (k < 0)
+ r = k;
}
- return 0;
+ return r;
}
static int parent_add_child(sd_device_enumerator *enumerator, const char *path) {
@@ -838,7 +840,7 @@ static int enumerator_scan_devices_all(sd_device_enumerator *enumerator) {
int device_enumerator_scan_devices(sd_device_enumerator *enumerator) {
sd_device *device;
- int r;
+ int r = 0, k;
assert(enumerator);
@@ -850,22 +852,22 @@ int device_enumerator_scan_devices(sd_device_enumerator *enumerator) {
sd_device_unref(device);
if (!set_isempty(enumerator->match_tag)) {
- r = enumerator_scan_devices_tags(enumerator);
- if (r < 0)
- return r;
+ k = enumerator_scan_devices_tags(enumerator);
+ if (k < 0)
+ r = k;
} else if (enumerator->match_parent) {
- r = enumerator_scan_devices_children(enumerator);
- if (r < 0)
- return r;
+ k = enumerator_scan_devices_children(enumerator);
+ if (k < 0)
+ r = k;
} else {
- r = enumerator_scan_devices_all(enumerator);
- if (r < 0)
- return r;
+ k = enumerator_scan_devices_all(enumerator);
+ if (k < 0)
+ r = k;
}
enumerator->scan_uptodate = true;
- return 0;
+ return r;
}
_public_ sd_device *sd_device_enumerator_get_device_first(sd_device_enumerator *enumerator) {
diff --git a/src/libsystemd/sd-device/device-internal.h b/src/libsystemd/sd-device/device-internal.h
index ab222e27de..9fad388953 100644
--- a/src/libsystemd/sd-device/device-internal.h
+++ b/src/libsystemd/sd-device/device-internal.h
@@ -76,6 +76,8 @@ struct sd_device {
char *subsystem;
bool subsystem_set; /* don't reread subsystem */
+ char *driver_subsystem; /* only set for the 'drivers' subsystem */
+ bool driver_subsystem_set; /* don't reread subsystem */
char *driver;
bool driver_set; /* don't reread driver */
diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c
index 5c9e00ed80..0c4ad966bd 100644
--- a/src/libsystemd/sd-device/sd-device.c
+++ b/src/libsystemd/sd-device/sd-device.c
@@ -75,6 +75,7 @@ _public_ sd_device *sd_device_unref(sd_device *device) {
free(device->devtype);
free(device->devname);
free(device->subsystem);
+ free(device->driver_subsystem);
free(device->driver);
free(device->id_filename);
free(device->properties_strv);
@@ -196,7 +197,7 @@ int device_set_syspath(sd_device *device, const char *_syspath, bool verify) {
return -errno;
}
} else {
- /* everything else just just needs to be a directory */
+ /* everything else just needs to be a directory */
if (!is_dir(syspath, false))
return -ENODEV;
}
@@ -258,7 +259,8 @@ _public_ int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum)
}
_public_ int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname) {
- char *syspath;
+ char *name, *syspath;
+ size_t len = 0;
assert_return(ret, -EINVAL);
assert_return(subsystem, -EINVAL);
@@ -297,33 +299,29 @@ _public_ int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *s
syspath = strjoina("/sys/bus/", subsys, "/drivers/", driver);
if (access(syspath, F_OK) >= 0)
return sd_device_new_from_syspath(ret, syspath);
- } else
- return -EINVAL;
- } else {
- char *name;
- size_t len = 0;
+ }
+ }
- /* translate sysname back to sysfs filename */
- name = strdupa(sysname);
- while (name[len] != '\0') {
- if (name[len] == '/')
- name[len] = '!';
+ /* translate sysname back to sysfs filename */
+ name = strdupa(sysname);
+ while (name[len] != '\0') {
+ if (name[len] == '/')
+ name[len] = '!';
- len++;
- }
+ len++;
+ }
- syspath = strjoina("/sys/subsystem/", subsystem, "/devices/", name);
- if (access(syspath, F_OK) >= 0)
- return sd_device_new_from_syspath(ret, syspath);
+ syspath = strjoina("/sys/subsystem/", subsystem, "/devices/", name);
+ if (access(syspath, F_OK) >= 0)
+ return sd_device_new_from_syspath(ret, syspath);
- syspath = strjoina("/sys/bus/", subsystem, "/devices/", name);
- if (access(syspath, F_OK) >= 0)
- return sd_device_new_from_syspath(ret, syspath);
+ syspath = strjoina("/sys/bus/", subsystem, "/devices/", name);
+ if (access(syspath, F_OK) >= 0)
+ return sd_device_new_from_syspath(ret, syspath);
- syspath = strjoina("/sys/class/", subsystem, "/", name);
- if (access(syspath, F_OK) >= 0)
- return sd_device_new_from_syspath(ret, syspath);
- }
+ syspath = strjoina("/sys/class/", subsystem, "/", name);
+ if (access(syspath, F_OK) >= 0)
+ return sd_device_new_from_syspath(ret, syspath);
return -ENODEV;
}
@@ -766,21 +764,45 @@ int device_set_subsystem(sd_device *device, const char *_subsystem) {
return 0;
}
+static int device_set_drivers_subsystem(sd_device *device, const char *_subsystem) {
+ _cleanup_free_ char *subsystem = NULL;
+ int r;
+
+ assert(device);
+ assert(_subsystem);
+ assert(*_subsystem);
+
+ subsystem = strdup(_subsystem);
+ if (!subsystem)
+ return -ENOMEM;
+
+ r = device_set_subsystem(device, "drivers");
+ if (r < 0)
+ return r;
+
+ free(device->driver_subsystem);
+ device->driver_subsystem = subsystem;
+ subsystem = NULL;
+
+ return 0;
+}
+
_public_ int sd_device_get_subsystem(sd_device *device, const char **ret) {
+ const char *syspath, *drivers = NULL;
+ int r;
+
assert_return(ret, -EINVAL);
assert_return(device, -EINVAL);
+ r = sd_device_get_syspath(device, &syspath);
+ if (r < 0)
+ return r;
+
if (!device->subsystem_set) {
_cleanup_free_ char *subsystem = NULL;
- const char *syspath;
char *path;
- int r;
/* read 'subsystem' link */
- r = sd_device_get_syspath(device, &syspath);
- if (r < 0)
- return r;
-
path = strjoina(syspath, "/subsystem");
r = readlink_value(path, &subsystem);
if (r >= 0)
@@ -788,16 +810,39 @@ _public_ int sd_device_get_subsystem(sd_device *device, const char **ret) {
/* use implicit names */
else if (path_startswith(device->devpath, "/module/"))
r = device_set_subsystem(device, "module");
- else if (strstr(device->devpath, "/drivers/"))
- r = device_set_subsystem(device, "drivers");
- else if (path_startswith(device->devpath, "/subsystem/") ||
- path_startswith(device->devpath, "/class/") ||
- path_startswith(device->devpath, "/bus/"))
+ else if (!(drivers = strstr(syspath, "/drivers/")) &&
+ (path_startswith(device->devpath, "/subsystem/") ||
+ path_startswith(device->devpath, "/class/") ||
+ path_startswith(device->devpath, "/bus/")))
r = device_set_subsystem(device, "subsystem");
if (r < 0 && r != -ENOENT)
return log_debug_errno(r, "sd-device: could not set subsystem for %s: %m", device->devpath);
device->subsystem_set = true;
+ } else if (!device->driver_subsystem_set)
+ drivers = strstr(syspath, "/drivers/");
+
+ if (!device->driver_subsystem_set) {
+ if (drivers) {
+ _cleanup_free_ char *subpath = NULL;
+
+ subpath = strndup(syspath, drivers - syspath);
+ if (!subpath)
+ r = -ENOMEM;
+ else {
+ const char *subsys;
+
+ subsys = strrchr(subpath, '/');
+ if (!subsys)
+ r = -EINVAL;
+ else
+ r = device_set_drivers_subsystem(device, subsys + 1);
+ }
+ if (r < 0 && r != -ENOENT)
+ return log_debug_errno(r, "sd-device: could not set subsystem for driver %s: %m", device->devpath);
+ }
+
+ device->driver_subsystem_set = true;
}
if (!device->subsystem)
@@ -1234,9 +1279,17 @@ int device_get_id_filename(sd_device *device, const char **ret) {
if (!subsystem)
return -EINVAL;
- r = asprintf(&id, "+%s:%s", subsystem, sysname);
- if (r < 0)
- return -ENOMEM;
+ if (streq(subsystem, "drivers")) {
+ /* the 'drivers' pseudo-subsystem is special, and needs the real subsystem
+ * encoded as well */
+ r = asprintf(&id, "+drivers:%s:%s", device->driver_subsystem, sysname);
+ if (r < 0)
+ return -ENOMEM;
+ } else {
+ r = asprintf(&id, "+%s:%s", subsystem, sysname);
+ if (r < 0)
+ return -ENOMEM;
+ }
}
device->id_filename = id;
diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c
index 7ba6527f63..9857f8b1fc 100644
--- a/src/libsystemd/sd-event/sd-event.c
+++ b/src/libsystemd/sd-event/sd-event.c
@@ -109,8 +109,8 @@ struct sd_event_source {
int64_t priority;
unsigned pending_index;
unsigned prepare_index;
- unsigned pending_iteration;
- unsigned prepare_iteration;
+ uint64_t pending_iteration;
+ uint64_t prepare_iteration;
LIST_FIELDS(sd_event_source, sources);
@@ -215,9 +215,8 @@ struct sd_event {
pid_t original_pid;
- unsigned iteration;
- dual_timestamp timestamp;
- usec_t timestamp_boottime;
+ uint64_t iteration;
+ triple_timestamp timestamp;
int state;
bool exit_requested:1;
@@ -1072,16 +1071,16 @@ _public_ int sd_event_add_time(
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(!event_pid_changed(e), -ECHILD);
- if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) &&
- !clock_boottime_supported())
+ if (!clock_supported(clock)) /* Checks whether the kernel supports the clock */
+ return -EOPNOTSUPP;
+
+ type = clock_to_event_source_type(clock); /* checks whether sd-event supports this clock */
+ if (type < 0)
return -EOPNOTSUPP;
if (!callback)
callback = time_exit_callback;
- type = clock_to_event_source_type(clock);
- assert_return(type >= 0, -EOPNOTSUPP);
-
d = event_get_clock_data(e, type);
assert(d);
@@ -2530,9 +2529,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
goto finish;
}
- dual_timestamp_get(&e->timestamp);
- if (clock_boottime_supported())
- e->timestamp_boottime = now(CLOCK_BOOTTIME);
+ triple_timestamp_get(&e->timestamp);
for (i = 0; i < m; i++) {
@@ -2573,7 +2570,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
if (r < 0)
goto finish;
- r = process_timer(e, e->timestamp_boottime, &e->boottime);
+ r = process_timer(e, e->timestamp.boottime, &e->boottime);
if (r < 0)
goto finish;
@@ -2585,7 +2582,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
if (r < 0)
goto finish;
- r = process_timer(e, e->timestamp_boottime, &e->boottime_alarm);
+ r = process_timer(e, e->timestamp.boottime, &e->boottime_alarm);
if (r < 0)
goto finish;
@@ -2759,43 +2756,24 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) {
assert_return(e, -EINVAL);
assert_return(usec, -EINVAL);
assert_return(!event_pid_changed(e), -ECHILD);
- assert_return(IN_SET(clock,
- CLOCK_REALTIME,
- CLOCK_REALTIME_ALARM,
- CLOCK_MONOTONIC,
- CLOCK_BOOTTIME,
- CLOCK_BOOTTIME_ALARM), -EOPNOTSUPP);
+ if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock))
+ return -EOPNOTSUPP;
+
+ /* Generate a clean error in case CLOCK_BOOTTIME is not available. Note that don't use clock_supported() here,
+ * for a reason: there are systems where CLOCK_BOOTTIME is supported, but CLOCK_BOOTTIME_ALARM is not, but for
+ * the purpose of getting the time this doesn't matter. */
if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && !clock_boottime_supported())
return -EOPNOTSUPP;
- if (!dual_timestamp_is_set(&e->timestamp)) {
+ if (!triple_timestamp_is_set(&e->timestamp)) {
/* Implicitly fall back to now() if we never ran
* before and thus have no cached time. */
*usec = now(clock);
return 1;
}
- switch (clock) {
-
- case CLOCK_REALTIME:
- case CLOCK_REALTIME_ALARM:
- *usec = e->timestamp.realtime;
- break;
-
- case CLOCK_MONOTONIC:
- *usec = e->timestamp.monotonic;
- break;
-
- case CLOCK_BOOTTIME:
- case CLOCK_BOOTTIME_ALARM:
- *usec = e->timestamp_boottime;
- break;
-
- default:
- assert_not_reached("Unknown clock?");
- }
-
+ *usec = triple_timestamp_by_clock(&e->timestamp, clock);
return 0;
}
@@ -2896,3 +2874,11 @@ _public_ int sd_event_get_watchdog(sd_event *e) {
return e->watchdog;
}
+
+_public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) {
+ assert_return(e, -EINVAL);
+ assert_return(!event_pid_changed(e), -ECHILD);
+
+ *ret = e->iteration;
+ return 0;
+}
diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c
new file mode 100644
index 0000000000..c3f527d657
--- /dev/null
+++ b/src/libsystemd/sd-id128/id128-util.c
@@ -0,0 +1,194 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "id128-util.h"
+#include "io-util.h"
+#include "stdio-util.h"
+
+char *id128_to_uuid_string(sd_id128_t id, char s[37]) {
+ unsigned n, k = 0;
+
+ assert(s);
+
+ /* Similar to sd_id128_to_string() but formats the result as UUID instead of plain hex chars */
+
+ for (n = 0; n < 16; n++) {
+
+ if (IN_SET(n, 4, 6, 8, 10))
+ s[k++] = '-';
+
+ s[k++] = hexchar(id.bytes[n] >> 4);
+ s[k++] = hexchar(id.bytes[n] & 0xF);
+ }
+
+ assert(k == 36);
+
+ s[k] = 0;
+
+ return s;
+}
+
+bool id128_is_valid(const char *s) {
+ size_t i, l;
+
+ assert(s);
+
+ l = strlen(s);
+ if (l == 32) {
+
+ /* Plain formatted 128bit hex string */
+
+ for (i = 0; i < l; i++) {
+ char c = s[i];
+
+ if (!(c >= '0' && c <= '9') &&
+ !(c >= 'a' && c <= 'z') &&
+ !(c >= 'A' && c <= 'Z'))
+ return false;
+ }
+
+ } else if (l == 36) {
+
+ /* Formatted UUID */
+
+ for (i = 0; i < l; i++) {
+ char c = s[i];
+
+ if ((i == 8 || i == 13 || i == 18 || i == 23)) {
+ if (c != '-')
+ return false;
+ } else {
+ if (!(c >= '0' && c <= '9') &&
+ !(c >= 'a' && c <= 'z') &&
+ !(c >= 'A' && c <= 'Z'))
+ return false;
+ }
+ }
+
+ } else
+ return false;
+
+ return true;
+}
+
+int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) {
+ char buffer[36 + 2];
+ ssize_t l;
+
+ assert(fd >= 0);
+ assert(f < _ID128_FORMAT_MAX);
+
+ /* Reads an 128bit ID from a file, which may either be in plain format (32 hex digits), or in UUID format, both
+ * optionally followed by a newline and nothing else. ID files should really be newline terminated, but if they
+ * aren't that's OK too, following the rule of "Be conservative in what you send, be liberal in what you
+ * accept". */
+
+ l = loop_read(fd, buffer, sizeof(buffer), false); /* we expect a short read of either 32/33 or 36/37 chars */
+ if (l < 0)
+ return (int) l;
+ if (l == 0) /* empty? */
+ return -ENOMEDIUM;
+
+ switch (l) {
+
+ case 33: /* plain UUID with trailing newline */
+ if (buffer[32] != '\n')
+ return -EINVAL;
+
+ /* fall through */
+ case 32: /* plain UUID without trailing newline */
+ if (f == ID128_UUID)
+ return -EINVAL;
+
+ buffer[32] = 0;
+ break;
+
+ case 37: /* RFC UUID with trailing newline */
+ if (buffer[36] != '\n')
+ return -EINVAL;
+
+ /* fall through */
+ case 36: /* RFC UUID without trailing newline */
+ if (f == ID128_PLAIN)
+ return -EINVAL;
+
+ buffer[36] = 0;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return sd_id128_from_string(buffer, ret);
+}
+
+int id128_read(const char *p, Id128Format f, sd_id128_t *ret) {
+ _cleanup_close_ int fd = -1;
+
+ fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0)
+ return -errno;
+
+ return id128_read_fd(fd, f, ret);
+}
+
+int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync) {
+ char buffer[36 + 2];
+ size_t sz;
+ int r;
+
+ assert(fd >= 0);
+ assert(f < _ID128_FORMAT_MAX);
+
+ if (f != ID128_UUID) {
+ sd_id128_to_string(id, buffer);
+ buffer[32] = '\n';
+ sz = 33;
+ } else {
+ id128_to_uuid_string(id, buffer);
+ buffer[36] = '\n';
+ sz = 37;
+ }
+
+ r = loop_write(fd, buffer, sz, false);
+ if (r < 0)
+ return r;
+
+ if (do_sync) {
+ if (fsync(fd) < 0)
+ return -errno;
+ }
+
+ return r;
+}
+
+int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync) {
+ _cleanup_close_ int fd = -1;
+
+ fd = open(p, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0444);
+ if (fd < 0)
+ return -errno;
+
+ return id128_write_fd(fd, f, id, do_sync);
+}
diff --git a/src/libsystemd/sd-id128/id128-util.h b/src/libsystemd/sd-id128/id128-util.h
new file mode 100644
index 0000000000..3ba59acbca
--- /dev/null
+++ b/src/libsystemd/sd-id128/id128-util.h
@@ -0,0 +1,45 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include "sd-id128.h"
+#include "macro.h"
+
+char *id128_to_uuid_string(sd_id128_t id, char s[37]);
+
+/* Like SD_ID128_FORMAT_STR, but formats as UUID, not in plain format */
+#define ID128_UUID_FORMAT_STR "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
+
+bool id128_is_valid(const char *s) _pure_;
+
+typedef enum Id128Format {
+ ID128_ANY,
+ ID128_PLAIN, /* formatted as 32 hex chars as-is */
+ ID128_UUID, /* formatted as 36 character uuid string */
+ _ID128_FORMAT_MAX,
+} Id128Format;
+
+int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret);
+int id128_read(const char *p, Id128Format f, sd_id128_t *ret);
+
+int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync);
+int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync);
diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c
index d9c0116f60..9f47d04e61 100644
--- a/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/libsystemd/sd-id128/sd-id128.c
@@ -25,6 +25,7 @@
#include "fd-util.h"
#include "hexdecoct.h"
+#include "id128-util.h"
#include "io-util.h"
#include "macro.h"
#include "random-util.h"
@@ -51,7 +52,6 @@ _public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) {
bool is_guid = false;
assert_return(s, -EINVAL);
- assert_return(ret, -EINVAL);
for (n = 0, i = 0; n < 16;) {
int a, b;
@@ -89,121 +89,57 @@ _public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) {
if (s[i] != 0)
return -EINVAL;
- *ret = t;
+ if (ret)
+ *ret = t;
return 0;
}
-static sd_id128_t make_v4_uuid(sd_id128_t id) {
- /* Stolen from generate_random_uuid() of drivers/char/random.c
- * in the kernel sources */
-
- /* Set UUID version to 4 --- truly random generation */
- id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40;
-
- /* Set the UUID variant to DCE */
- id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80;
-
- return id;
-}
-
_public_ int sd_id128_get_machine(sd_id128_t *ret) {
- static thread_local sd_id128_t saved_machine_id;
- static thread_local bool saved_machine_id_valid = false;
- _cleanup_close_ int fd = -1;
- char buf[33];
- unsigned j;
- sd_id128_t t;
+ static thread_local sd_id128_t saved_machine_id = {};
int r;
assert_return(ret, -EINVAL);
- if (saved_machine_id_valid) {
- *ret = saved_machine_id;
- return 0;
- }
-
- fd = open("/etc/machine-id", O_RDONLY|O_CLOEXEC|O_NOCTTY);
- if (fd < 0)
- return -errno;
-
- r = loop_read_exact(fd, buf, 33, false);
- if (r < 0)
- return r;
- if (buf[32] !='\n')
- return -EIO;
-
- for (j = 0; j < 16; j++) {
- int a, b;
-
- a = unhexchar(buf[j*2]);
- b = unhexchar(buf[j*2+1]);
-
- if (a < 0 || b < 0)
- return -EIO;
+ if (sd_id128_is_null(saved_machine_id)) {
+ r = id128_read("/etc/machine-id", ID128_PLAIN, &saved_machine_id);
+ if (r < 0)
+ return r;
- t.bytes[j] = a << 4 | b;
+ if (sd_id128_is_null(saved_machine_id))
+ return -EINVAL;
}
- saved_machine_id = t;
- saved_machine_id_valid = true;
-
- *ret = t;
+ *ret = saved_machine_id;
return 0;
}
_public_ int sd_id128_get_boot(sd_id128_t *ret) {
- static thread_local sd_id128_t saved_boot_id;
- static thread_local bool saved_boot_id_valid = false;
- _cleanup_close_ int fd = -1;
- char buf[36];
- unsigned j;
- sd_id128_t t;
- char *p;
+ static thread_local sd_id128_t saved_boot_id = {};
int r;
assert_return(ret, -EINVAL);
- if (saved_boot_id_valid) {
- *ret = saved_boot_id;
- return 0;
+ if (sd_id128_is_null(saved_boot_id)) {
+ r = id128_read("/proc/sys/kernel/random/boot_id", ID128_UUID, &saved_boot_id);
+ if (r < 0)
+ return r;
}
- fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY);
- if (fd < 0)
- return -errno;
-
- r = loop_read_exact(fd, buf, 36, false);
- if (r < 0)
- return r;
-
- for (j = 0, p = buf; j < 16; j++) {
- int a, b;
-
- if (p >= buf + 35)
- return -EIO;
-
- if (*p == '-') {
- p++;
- if (p >= buf + 35)
- return -EIO;
- }
-
- a = unhexchar(p[0]);
- b = unhexchar(p[1]);
-
- if (a < 0 || b < 0)
- return -EIO;
+ *ret = saved_boot_id;
+ return 0;
+}
- t.bytes[j] = a << 4 | b;
+static sd_id128_t make_v4_uuid(sd_id128_t id) {
+ /* Stolen from generate_random_uuid() of drivers/char/random.c
+ * in the kernel sources */
- p += 2;
- }
+ /* Set UUID version to 4 --- truly random generation */
+ id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40;
- saved_boot_id = t;
- saved_boot_id_valid = true;
+ /* Set the UUID variant to DCE */
+ id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80;
- *ret = t;
- return 0;
+ return id;
}
_public_ int sd_id128_randomize(sd_id128_t *ret) {
diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c
index 9d4f187502..3fcefada3f 100644
--- a/src/libsystemd/sd-login/sd-login.c
+++ b/src/libsystemd/sd-login/sd-login.c
@@ -124,7 +124,7 @@ _public_ int sd_pid_get_cgroup(pid_t pid, char **cgroup) {
/* The internal APIs return the empty string for the root
* cgroup, let's return the "/" in the public APIs instead, as
- * that's easier and less ambigious for people to grok. */
+ * that's easier and less ambiguous for people to grok. */
if (isempty(c)) {
free(c);
c = strdup("/");
diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c
index 86d8dee867..df3b3c922e 100644
--- a/src/libsystemd/sd-netlink/netlink-message.c
+++ b/src/libsystemd/sd-netlink/netlink-message.c
@@ -120,7 +120,9 @@ sd_netlink_message *sd_netlink_message_ref(sd_netlink_message *m) {
}
sd_netlink_message *sd_netlink_message_unref(sd_netlink_message *m) {
- if (m && REFCNT_DEC(m->n_ref) == 0) {
+ sd_netlink_message *t;
+
+ while (m && REFCNT_DEC(m->n_ref) == 0) {
unsigned i;
free(m->hdr);
@@ -128,9 +130,9 @@ sd_netlink_message *sd_netlink_message_unref(sd_netlink_message *m) {
for (i = 0; i <= m->n_containers; i++)
free(m->containers[i].attributes);
- sd_netlink_message_unref(m->next);
-
- free(m);
+ t = m;
+ m = m->next;
+ free(t);
}
return NULL;
diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c
index 3a4bac2ced..566a050432 100644
--- a/src/libsystemd/sd-netlink/netlink-types.c
+++ b/src/libsystemd/sd-netlink/netlink-types.c
@@ -278,6 +278,10 @@ static const NLType rtnl_link_info_data_ip6tnl_types[] = {
[IFLA_IPTUN_FLOWINFO] = { .type = NETLINK_TYPE_U32 },
};
+static const NLType rtnl_link_info_data_vrf_types[] = {
+ [IFLA_VRF_TABLE] = { .type = NETLINK_TYPE_U32 },
+};
+
/* these strings must match the .kind entries in the kernel */
static const char* const nl_union_link_info_data_table[] = {
[NL_UNION_LINK_INFO_DATA_BOND] = "bond",
@@ -298,6 +302,7 @@ static const char* const nl_union_link_info_data_table[] = {
[NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = "vti",
[NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = "vti6",
[NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = "ip6tnl",
+ [NL_UNION_LINK_INFO_DATA_VRF] = "vrf",
};
DEFINE_STRING_TABLE_LOOKUP(nl_union_link_info_data, NLUnionLinkInfoData);
@@ -338,6 +343,9 @@ static const NLTypeSystem rtnl_link_info_data_type_systems[] = {
[NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ip6tnl_types),
.types = rtnl_link_info_data_ip6tnl_types },
+ [NL_UNION_LINK_INFO_DATA_VRF] = { .count = ELEMENTSOF(rtnl_link_info_data_vrf_types),
+ .types = rtnl_link_info_data_vrf_types },
+
};
static const NLTypeSystemUnion rtnl_link_info_data_type_system_union = {
diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h
index ecb20bfcdc..7c0e598b26 100644
--- a/src/libsystemd/sd-netlink/netlink-types.h
+++ b/src/libsystemd/sd-netlink/netlink-types.h
@@ -86,6 +86,7 @@ typedef enum NLUnionLinkInfoData {
NL_UNION_LINK_INFO_DATA_VTI_TUNNEL,
NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL,
NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL,
+ NL_UNION_LINK_INFO_DATA_VRF,
_NL_UNION_LINK_INFO_DATA_MAX,
_NL_UNION_LINK_INFO_DATA_INVALID = -1
} NLUnionLinkInfoData;
diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c
index 91701405a5..43114eb825 100644
--- a/src/libsystemd/sd-netlink/sd-netlink.c
+++ b/src/libsystemd/sd-netlink/sd-netlink.c
@@ -144,7 +144,10 @@ int sd_netlink_open(sd_netlink **ret) {
return 0;
}
-int sd_netlink_inc_rcvbuf(const sd_netlink *const rtnl, const int size) {
+int sd_netlink_inc_rcvbuf(sd_netlink *rtnl, size_t size) {
+ assert_return(rtnl, -EINVAL);
+ assert_return(!rtnl_pid_changed(rtnl), -ECHILD);
+
return fd_inc_rcvbuf(rtnl->fd, size);
}
diff --git a/src/libsystemd/sd-resolve/sd-resolve.c b/src/libsystemd/sd-resolve/sd-resolve.c
index d8303e2e69..60aa55de3b 100644
--- a/src/libsystemd/sd-resolve/sd-resolve.c
+++ b/src/libsystemd/sd-resolve/sd-resolve.c
@@ -585,9 +585,7 @@ static void resolve_free(sd_resolve *resolve) {
(void) pthread_join(resolve->workers[i], NULL);
/* Close all communication channels */
- for (i = 0; i < _FD_MAX; i++)
- safe_close(resolve->fds[i]);
-
+ close_many(resolve->fds, _FD_MAX);
free(resolve);
}
diff --git a/src/libudev/libudev-device.c b/src/libudev/libudev-device.c
index 814e016800..995bf56586 100644
--- a/src/libudev/libudev-device.c
+++ b/src/libudev/libudev-device.c
@@ -619,7 +619,7 @@ _public_ const char *udev_device_get_syspath(struct udev_device *udev_device)
*
* Get the kernel device name in /sys.
*
- * Returns: the name string of the device device
+ * Returns: the name string of the device
**/
_public_ const char *udev_device_get_sysname(struct udev_device *udev_device)
{
diff --git a/src/libudev/libudev-monitor.c b/src/libudev/libudev-monitor.c
index f870eba9eb..1f9d16c450 100644
--- a/src/libudev/libudev-monitor.c
+++ b/src/libudev/libudev-monitor.c
@@ -650,9 +650,9 @@ retry:
if (memcmp(buf.raw, "libudev", 8) == 0) {
/* udev message needs proper version magic */
- if (buf.nlh.magic != htonl(UDEV_MONITOR_MAGIC)) {
+ if (buf.nlh.magic != htobe32(UDEV_MONITOR_MAGIC)) {
log_debug("unrecognized message signature (%x != %x)",
- buf.nlh.magic, htonl(UDEV_MONITOR_MAGIC));
+ buf.nlh.magic, htobe32(UDEV_MONITOR_MAGIC));
return NULL;
}
if (buf.nlh.properties_off+32 > (size_t)buflen) {
@@ -715,7 +715,7 @@ int udev_monitor_send_device(struct udev_monitor *udev_monitor,
ssize_t blen, count;
struct udev_monitor_netlink_header nlh = {
.prefix = "libudev",
- .magic = htonl(UDEV_MONITOR_MAGIC),
+ .magic = htobe32(UDEV_MONITOR_MAGIC),
.header_size = sizeof nlh,
};
struct iovec iov[2] = {
@@ -736,19 +736,19 @@ int udev_monitor_send_device(struct udev_monitor *udev_monitor,
/* fill in versioned header */
val = udev_device_get_subsystem(udev_device);
- nlh.filter_subsystem_hash = htonl(util_string_hash32(val));
+ nlh.filter_subsystem_hash = htobe32(util_string_hash32(val));
val = udev_device_get_devtype(udev_device);
if (val != NULL)
- nlh.filter_devtype_hash = htonl(util_string_hash32(val));
+ nlh.filter_devtype_hash = htobe32(util_string_hash32(val));
/* add tag bloom filter */
tag_bloom_bits = 0;
udev_list_entry_foreach(list_entry, udev_device_get_tags_list_entry(udev_device))
tag_bloom_bits |= util_string_bloom64(udev_list_entry_get_name(list_entry));
if (tag_bloom_bits > 0) {
- nlh.filter_tag_bloom_hi = htonl(tag_bloom_bits >> 32);
- nlh.filter_tag_bloom_lo = htonl(tag_bloom_bits & 0xffffffff);
+ nlh.filter_tag_bloom_hi = htobe32(tag_bloom_bits >> 32);
+ nlh.filter_tag_bloom_lo = htobe32(tag_bloom_bits & 0xffffffff);
}
/* add properties list */
diff --git a/src/locale/keymap-util.c b/src/locale/keymap-util.c
new file mode 100644
index 0000000000..a6bcd1ad54
--- /dev/null
+++ b/src/locale/keymap-util.c
@@ -0,0 +1,724 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+ Copyright 2013 Kay Sievers
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "def.h"
+#include "env-util.h"
+#include "fd-util.h"
+#include "fileio-label.h"
+#include "fileio.h"
+#include "keymap-util.h"
+#include "locale-util.h"
+#include "macro.h"
+#include "mkdir.h"
+#include "string-util.h"
+#include "strv.h"
+
+static bool startswith_comma(const char *s, const char *prefix) {
+ s = startswith(s, prefix);
+ if (!s)
+ return false;
+
+ return *s == ',' || *s == '\0';
+}
+
+static const char* strnulldash(const char *s) {
+ return isempty(s) || streq(s, "-") ? NULL : s;
+}
+
+static const char* systemd_kbd_model_map(void) {
+ const char* s;
+
+ s = getenv("SYSTEMD_KBD_MODEL_MAP");
+ if (s)
+ return s;
+
+ return SYSTEMD_KBD_MODEL_MAP;
+}
+
+static const char* systemd_language_fallback_map(void) {
+ const char* s;
+
+ s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
+ if (s)
+ return s;
+
+ return SYSTEMD_LANGUAGE_FALLBACK_MAP;
+}
+
+static void context_free_x11(Context *c) {
+ c->x11_layout = mfree(c->x11_layout);
+ c->x11_options = mfree(c->x11_options);
+ c->x11_model = mfree(c->x11_model);
+ c->x11_variant = mfree(c->x11_variant);
+}
+
+static void context_free_vconsole(Context *c) {
+ c->vc_keymap = mfree(c->vc_keymap);
+ c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
+}
+
+static void context_free_locale(Context *c) {
+ int p;
+
+ for (p = 0; p < _VARIABLE_LC_MAX; p++)
+ c->locale[p] = mfree(c->locale[p]);
+}
+
+void context_free(Context *c) {
+ context_free_locale(c);
+ context_free_x11(c);
+ context_free_vconsole(c);
+};
+
+void locale_simplify(Context *c) {
+ int p;
+
+ for (p = VARIABLE_LANG+1; p < _VARIABLE_LC_MAX; p++)
+ if (isempty(c->locale[p]) || streq_ptr(c->locale[VARIABLE_LANG], c->locale[p]))
+ c->locale[p] = mfree(c->locale[p]);
+}
+
+static int locale_read_data(Context *c) {
+ int r;
+
+ context_free_locale(c);
+
+ r = parse_env_file("/etc/locale.conf", NEWLINE,
+ "LANG", &c->locale[VARIABLE_LANG],
+ "LANGUAGE", &c->locale[VARIABLE_LANGUAGE],
+ "LC_CTYPE", &c->locale[VARIABLE_LC_CTYPE],
+ "LC_NUMERIC", &c->locale[VARIABLE_LC_NUMERIC],
+ "LC_TIME", &c->locale[VARIABLE_LC_TIME],
+ "LC_COLLATE", &c->locale[VARIABLE_LC_COLLATE],
+ "LC_MONETARY", &c->locale[VARIABLE_LC_MONETARY],
+ "LC_MESSAGES", &c->locale[VARIABLE_LC_MESSAGES],
+ "LC_PAPER", &c->locale[VARIABLE_LC_PAPER],
+ "LC_NAME", &c->locale[VARIABLE_LC_NAME],
+ "LC_ADDRESS", &c->locale[VARIABLE_LC_ADDRESS],
+ "LC_TELEPHONE", &c->locale[VARIABLE_LC_TELEPHONE],
+ "LC_MEASUREMENT", &c->locale[VARIABLE_LC_MEASUREMENT],
+ "LC_IDENTIFICATION", &c->locale[VARIABLE_LC_IDENTIFICATION],
+ NULL);
+
+ if (r == -ENOENT) {
+ int p;
+
+ /* Fill in what we got passed from systemd. */
+ for (p = 0; p < _VARIABLE_LC_MAX; p++) {
+ const char *name;
+
+ name = locale_variable_to_string(p);
+ assert(name);
+
+ r = free_and_strdup(&c->locale[p], empty_to_null(getenv(name)));
+ if (r < 0)
+ return r;
+ }
+
+ r = 0;
+ }
+
+ locale_simplify(c);
+ return r;
+}
+
+static int vconsole_read_data(Context *c) {
+ int r;
+
+ context_free_vconsole(c);
+
+ r = parse_env_file("/etc/vconsole.conf", NEWLINE,
+ "KEYMAP", &c->vc_keymap,
+ "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
+ NULL);
+
+ if (r < 0 && r != -ENOENT)
+ return r;
+
+ return 0;
+}
+
+static int x11_read_data(Context *c) {
+ _cleanup_fclose_ FILE *f;
+ char line[LINE_MAX];
+ bool in_section = false;
+ int r;
+
+ context_free_x11(c);
+
+ f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
+ if (!f)
+ return errno == ENOENT ? 0 : -errno;
+
+ while (fgets(line, sizeof(line), f)) {
+ char *l;
+
+ char_array_0(line);
+ l = strstrip(line);
+
+ if (l[0] == 0 || l[0] == '#')
+ continue;
+
+ if (in_section && first_word(l, "Option")) {
+ _cleanup_strv_free_ char **a = NULL;
+
+ r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
+ if (r < 0)
+ return r;
+
+ if (strv_length(a) == 3) {
+ char **p = NULL;
+
+ if (streq(a[1], "XkbLayout"))
+ p = &c->x11_layout;
+ else if (streq(a[1], "XkbModel"))
+ p = &c->x11_model;
+ else if (streq(a[1], "XkbVariant"))
+ p = &c->x11_variant;
+ else if (streq(a[1], "XkbOptions"))
+ p = &c->x11_options;
+
+ if (p) {
+ free(*p);
+ *p = a[2];
+ a[2] = NULL;
+ }
+ }
+
+ } else if (!in_section && first_word(l, "Section")) {
+ _cleanup_strv_free_ char **a = NULL;
+
+ r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
+ if (r < 0)
+ return -ENOMEM;
+
+ if (strv_length(a) == 2 && streq(a[1], "InputClass"))
+ in_section = true;
+
+ } else if (in_section && first_word(l, "EndSection"))
+ in_section = false;
+ }
+
+ return 0;
+}
+
+int context_read_data(Context *c) {
+ int r, q, p;
+
+ r = locale_read_data(c);
+ q = vconsole_read_data(c);
+ p = x11_read_data(c);
+
+ return r < 0 ? r : q < 0 ? q : p;
+}
+
+int locale_write_data(Context *c, char ***settings) {
+ int r, p;
+ _cleanup_strv_free_ char **l = NULL;
+
+ /* Set values will be returned as strv in *settings on success. */
+
+ r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
+ if (r < 0 && r != -ENOENT)
+ return r;
+
+ for (p = 0; p < _VARIABLE_LC_MAX; p++) {
+ _cleanup_free_ char *t = NULL;
+ char **u;
+ const char *name;
+
+ name = locale_variable_to_string(p);
+ assert(name);
+
+ if (isempty(c->locale[p])) {
+ l = strv_env_unset(l, name);
+ continue;
+ }
+
+ if (asprintf(&t, "%s=%s", name, c->locale[p]) < 0)
+ return -ENOMEM;
+
+ u = strv_env_set(l, t);
+ if (!u)
+ return -ENOMEM;
+
+ strv_free(l);
+ l = u;
+ }
+
+ if (strv_isempty(l)) {
+ if (unlink("/etc/locale.conf") < 0)
+ return errno == ENOENT ? 0 : -errno;
+
+ return 0;
+ }
+
+ r = write_env_file_label("/etc/locale.conf", l);
+ if (r < 0)
+ return r;
+
+ *settings = l;
+ l = NULL;
+ return 0;
+}
+
+int vconsole_write_data(Context *c) {
+ int r;
+ _cleanup_strv_free_ char **l = NULL;
+
+ r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
+ if (r < 0 && r != -ENOENT)
+ return r;
+
+ if (isempty(c->vc_keymap))
+ l = strv_env_unset(l, "KEYMAP");
+ else {
+ _cleanup_free_ char *s = NULL;
+ char **u;
+
+ s = strappend("KEYMAP=", c->vc_keymap);
+ if (!s)
+ return -ENOMEM;
+
+ u = strv_env_set(l, s);
+ if (!u)
+ return -ENOMEM;
+
+ strv_free(l);
+ l = u;
+ }
+
+ if (isempty(c->vc_keymap_toggle))
+ l = strv_env_unset(l, "KEYMAP_TOGGLE");
+ else {
+ _cleanup_free_ char *s = NULL;
+ char **u;
+
+ s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
+ if (!s)
+ return -ENOMEM;
+
+ u = strv_env_set(l, s);
+ if (!u)
+ return -ENOMEM;
+
+ strv_free(l);
+ l = u;
+ }
+
+ if (strv_isempty(l)) {
+ if (unlink("/etc/vconsole.conf") < 0)
+ return errno == ENOENT ? 0 : -errno;
+
+ return 0;
+ }
+
+ return write_env_file_label("/etc/vconsole.conf", l);
+}
+
+int x11_write_data(Context *c) {
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *temp_path = NULL;
+ int r;
+
+ if (isempty(c->x11_layout) &&
+ isempty(c->x11_model) &&
+ isempty(c->x11_variant) &&
+ isempty(c->x11_options)) {
+
+ if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
+ return errno == ENOENT ? 0 : -errno;
+
+ return 0;
+ }
+
+ mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
+
+ r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
+ if (r < 0)
+ return r;
+
+ fchmod(fileno(f), 0644);
+
+ fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
+ "# manually too freely.\n"
+ "Section \"InputClass\"\n"
+ " Identifier \"system-keyboard\"\n"
+ " MatchIsKeyboard \"on\"\n", f);
+
+ if (!isempty(c->x11_layout))
+ fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
+
+ if (!isempty(c->x11_model))
+ fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
+
+ if (!isempty(c->x11_variant))
+ fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
+
+ if (!isempty(c->x11_options))
+ fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
+
+ fputs("EndSection\n", f);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
+
+ if (temp_path)
+ (void) unlink(temp_path);
+
+ return r;
+}
+
+static int read_next_mapping(const char* filename,
+ unsigned min_fields, unsigned max_fields,
+ FILE *f, unsigned *n, char ***a) {
+ assert(f);
+ assert(n);
+ assert(a);
+
+ for (;;) {
+ char line[LINE_MAX];
+ char *l, **b;
+ int r;
+ size_t length;
+
+ errno = 0;
+ if (!fgets(line, sizeof(line), f)) {
+
+ if (ferror(f))
+ return errno > 0 ? -errno : -EIO;
+
+ return 0;
+ }
+
+ (*n)++;
+
+ l = strstrip(line);
+ if (l[0] == 0 || l[0] == '#')
+ continue;
+
+ r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES);
+ if (r < 0)
+ return r;
+
+ length = strv_length(b);
+ if (length < min_fields || length > max_fields) {
+ log_error("Invalid line %s:%u, ignoring.", filename, *n);
+ strv_free(b);
+ continue;
+
+ }
+
+ *a = b;
+ return 1;
+ }
+}
+
+int vconsole_convert_to_x11(Context *c) {
+ const char *map;
+ int modified = -1;
+
+ map = systemd_kbd_model_map();
+
+ if (isempty(c->vc_keymap)) {
+ modified =
+ !isempty(c->x11_layout) ||
+ !isempty(c->x11_model) ||
+ !isempty(c->x11_variant) ||
+ !isempty(c->x11_options);
+
+ context_free_x11(c);
+ } else {
+ _cleanup_fclose_ FILE *f = NULL;
+ unsigned n = 0;
+
+ f = fopen(map, "re");
+ if (!f)
+ return -errno;
+
+ for (;;) {
+ _cleanup_strv_free_ char **a = NULL;
+ int r;
+
+ r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (!streq(c->vc_keymap, a[0]))
+ continue;
+
+ if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
+ !streq_ptr(c->x11_model, strnulldash(a[2])) ||
+ !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
+ !streq_ptr(c->x11_options, strnulldash(a[4]))) {
+
+ if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
+ free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
+ free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
+ free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
+ return -ENOMEM;
+
+ modified = true;
+ }
+
+ break;
+ }
+ }
+
+ if (modified > 0)
+ log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
+ strempty(c->x11_layout),
+ strempty(c->x11_model),
+ strempty(c->x11_variant),
+ strempty(c->x11_options));
+ else if (modified < 0)
+ log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
+ c->vc_keymap);
+ else
+ log_debug("X11 keyboard layout did not need to be modified.");
+
+ return modified > 0;
+}
+
+int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
+ const char *dir;
+ _cleanup_free_ char *n;
+
+ if (x11_variant)
+ n = strjoin(x11_layout, "-", x11_variant, NULL);
+ else
+ n = strdup(x11_layout);
+ if (!n)
+ return -ENOMEM;
+
+ NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
+ _cleanup_free_ char *p = NULL, *pz = NULL;
+ bool uncompressed;
+
+ p = strjoin(dir, "xkb/", n, ".map", NULL);
+ pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
+ if (!p || !pz)
+ return -ENOMEM;
+
+ uncompressed = access(p, F_OK) == 0;
+ if (uncompressed || access(pz, F_OK) == 0) {
+ log_debug("Found converted keymap %s at %s",
+ n, uncompressed ? p : pz);
+
+ *new_keymap = n;
+ n = NULL;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int find_legacy_keymap(Context *c, char **new_keymap) {
+ const char *map;
+ _cleanup_fclose_ FILE *f = NULL;
+ unsigned n = 0;
+ unsigned best_matching = 0;
+ int r;
+
+ assert(!isempty(c->x11_layout));
+
+ map = systemd_kbd_model_map();
+
+ f = fopen(map, "re");
+ if (!f)
+ return -errno;
+
+ for (;;) {
+ _cleanup_strv_free_ char **a = NULL;
+ unsigned matching = 0;
+
+ r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ /* Determine how well matching this entry is */
+ if (streq(c->x11_layout, a[1]))
+ /* If we got an exact match, this is best */
+ matching = 10;
+ else {
+ /* We have multiple X layouts, look for an
+ * entry that matches our key with everything
+ * but the first layout stripped off. */
+ if (startswith_comma(c->x11_layout, a[1]))
+ matching = 5;
+ else {
+ char *x;
+
+ /* If that didn't work, strip off the
+ * other layouts from the entry, too */
+ x = strndupa(a[1], strcspn(a[1], ","));
+ if (startswith_comma(c->x11_layout, x))
+ matching = 1;
+ }
+ }
+
+ if (matching > 0) {
+ if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
+ matching++;
+
+ if (streq_ptr(c->x11_variant, a[3])) {
+ matching++;
+
+ if (streq_ptr(c->x11_options, a[4]))
+ matching++;
+ }
+ }
+ }
+
+ /* The best matching entry so far, then let's save that */
+ if (matching >= MAX(best_matching, 1u)) {
+ log_debug("Found legacy keymap %s with score %u",
+ a[0], matching);
+
+ if (matching > best_matching) {
+ best_matching = matching;
+
+ r = free_and_strdup(new_keymap, a[0]);
+ if (r < 0)
+ return r;
+ }
+ }
+ }
+
+ if (best_matching < 10 && c->x11_layout) {
+ /* The best match is only the first part of the X11
+ * keymap. Check if we have a converted map which
+ * matches just the first layout.
+ */
+ char *l, *v = NULL, *converted;
+
+ l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
+ if (c->x11_variant)
+ v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
+ r = find_converted_keymap(l, v, &converted);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ free(*new_keymap);
+ *new_keymap = converted;
+ }
+ }
+
+ return (bool) *new_keymap;
+}
+
+int find_language_fallback(const char *lang, char **language) {
+ const char *map;
+ _cleanup_fclose_ FILE *f = NULL;
+ unsigned n = 0;
+
+ assert(lang);
+ assert(language);
+
+ map = systemd_language_fallback_map();
+
+ f = fopen(map, "re");
+ if (!f)
+ return -errno;
+
+ for (;;) {
+ _cleanup_strv_free_ char **a = NULL;
+ int r;
+
+ r = read_next_mapping(map, 2, 2, f, &n, &a);
+ if (r <= 0)
+ return r;
+
+ if (streq(lang, a[0])) {
+ assert(strv_length(a) == 2);
+ *language = a[1];
+ a[1] = NULL;
+ return 1;
+ }
+ }
+
+ assert_not_reached("should not be here");
+}
+
+int x11_convert_to_vconsole(Context *c) {
+ bool modified = false;
+
+ if (isempty(c->x11_layout)) {
+ modified =
+ !isempty(c->vc_keymap) ||
+ !isempty(c->vc_keymap_toggle);
+
+ context_free_vconsole(c);
+ } else {
+ char *new_keymap = NULL;
+ int r;
+
+ r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
+ if (r < 0)
+ return r;
+ else if (r == 0) {
+ r = find_legacy_keymap(c, &new_keymap);
+ if (r < 0)
+ return r;
+ }
+ if (r == 0)
+ /* We search for layout-variant match first, but then we also look
+ * for anything which matches just the layout. So it's accurate to say
+ * that we couldn't find anything which matches the layout. */
+ log_notice("No conversion to virtual console map found for \"%s\".",
+ c->x11_layout);
+
+ if (!streq_ptr(c->vc_keymap, new_keymap)) {
+ free(c->vc_keymap);
+ c->vc_keymap = new_keymap;
+ c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
+ modified = true;
+ } else
+ free(new_keymap);
+ }
+
+ if (modified)
+ log_info("Changing virtual console keymap to '%s' toggle '%s'",
+ strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
+ else
+ log_debug("Virtual console keymap was not modified.");
+
+ return modified;
+}
diff --git a/src/locale/keymap-util.h b/src/locale/keymap-util.h
new file mode 100644
index 0000000000..20ef2a4a34
--- /dev/null
+++ b/src/locale/keymap-util.h
@@ -0,0 +1,46 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+ Copyright 2013 Kay Sievers
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "locale-util.h"
+
+typedef struct Context {
+ char *locale[_VARIABLE_LC_MAX];
+
+ char *x11_layout;
+ char *x11_model;
+ char *x11_variant;
+ char *x11_options;
+
+ char *vc_keymap;
+ char *vc_keymap_toggle;
+} Context;
+
+int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap);
+int find_legacy_keymap(Context *c, char **new_keymap);
+int find_language_fallback(const char *lang, char **language);
+
+int context_read_data(Context *c);
+void context_free(Context *c);
+int vconsole_convert_to_x11(Context *c);
+int vconsole_write_data(Context *c);
+int x11_convert_to_vconsole(Context *c);
+int x11_write_data(Context *c);
+void locale_simplify(Context *c);
+int locale_write_data(Context *c, char ***settings);
diff --git a/src/locale/localectl.c b/src/locale/localectl.c
index 4865335349..81afb4909f 100644
--- a/src/locale/localectl.c
+++ b/src/locale/localectl.c
@@ -656,7 +656,7 @@ static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
}
int main(int argc, char*argv[]) {
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ sd_bus *bus = NULL;
int r;
setlocale(LC_ALL, "");
@@ -676,6 +676,7 @@ int main(int argc, char*argv[]) {
r = localectl_main(bus, argc, argv);
finish:
+ sd_bus_flush_close_unref(bus);
pager_close();
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
diff --git a/src/locale/localed.c b/src/locale/localed.c
index 6af59fc830..298f176e40 100644
--- a/src/locale/localed.c
+++ b/src/locale/localed.c
@@ -34,288 +34,16 @@
#include "bus-message.h"
#include "bus-util.h"
#include "def.h"
-#include "env-util.h"
-#include "fd-util.h"
-#include "fileio-label.h"
-#include "fileio.h"
+#include "keymap-util.h"
#include "locale-util.h"
-#include "mkdir.h"
+#include "macro.h"
#include "path-util.h"
#include "selinux-util.h"
+#include "string-util.h"
#include "strv.h"
#include "user-util.h"
-#include "util.h"
-
-enum {
- /* We don't list LC_ALL here on purpose. People should be
- * using LANG instead. */
- LOCALE_LANG,
- LOCALE_LANGUAGE,
- LOCALE_LC_CTYPE,
- LOCALE_LC_NUMERIC,
- LOCALE_LC_TIME,
- LOCALE_LC_COLLATE,
- LOCALE_LC_MONETARY,
- LOCALE_LC_MESSAGES,
- LOCALE_LC_PAPER,
- LOCALE_LC_NAME,
- LOCALE_LC_ADDRESS,
- LOCALE_LC_TELEPHONE,
- LOCALE_LC_MEASUREMENT,
- LOCALE_LC_IDENTIFICATION,
- _LOCALE_MAX
-};
-
-static const char * const names[_LOCALE_MAX] = {
- [LOCALE_LANG] = "LANG",
- [LOCALE_LANGUAGE] = "LANGUAGE",
- [LOCALE_LC_CTYPE] = "LC_CTYPE",
- [LOCALE_LC_NUMERIC] = "LC_NUMERIC",
- [LOCALE_LC_TIME] = "LC_TIME",
- [LOCALE_LC_COLLATE] = "LC_COLLATE",
- [LOCALE_LC_MONETARY] = "LC_MONETARY",
- [LOCALE_LC_MESSAGES] = "LC_MESSAGES",
- [LOCALE_LC_PAPER] = "LC_PAPER",
- [LOCALE_LC_NAME] = "LC_NAME",
- [LOCALE_LC_ADDRESS] = "LC_ADDRESS",
- [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE",
- [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT",
- [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
-};
-
-typedef struct Context {
- char *locale[_LOCALE_MAX];
-
- char *x11_layout;
- char *x11_model;
- char *x11_variant;
- char *x11_options;
-
- char *vc_keymap;
- char *vc_keymap_toggle;
-
- Hashmap *polkit_registry;
-} Context;
-
-static bool startswith_comma(const char *s, const char *prefix) {
- const char *t;
-
- return s && (t = startswith(s, prefix)) && (*t == ',');
-}
-
-static void context_free_x11(Context *c) {
- c->x11_layout = mfree(c->x11_layout);
- c->x11_options = mfree(c->x11_options);
- c->x11_model = mfree(c->x11_model);
- c->x11_variant = mfree(c->x11_variant);
-}
-
-static void context_free_vconsole(Context *c) {
- c->vc_keymap = mfree(c->vc_keymap);
- c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
-}
-
-static void context_free_locale(Context *c) {
- int p;
-
- for (p = 0; p < _LOCALE_MAX; p++)
- c->locale[p] = mfree(c->locale[p]);
-}
-
-static void context_free(Context *c) {
- context_free_locale(c);
- context_free_x11(c);
- context_free_vconsole(c);
-
- bus_verify_polkit_async_registry_free(c->polkit_registry);
-};
-
-static void locale_simplify(Context *c) {
- int p;
-
- for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++)
- if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p]))
- c->locale[p] = mfree(c->locale[p]);
-}
-
-static int locale_read_data(Context *c) {
- int r;
-
- context_free_locale(c);
-
- r = parse_env_file("/etc/locale.conf", NEWLINE,
- "LANG", &c->locale[LOCALE_LANG],
- "LANGUAGE", &c->locale[LOCALE_LANGUAGE],
- "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE],
- "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC],
- "LC_TIME", &c->locale[LOCALE_LC_TIME],
- "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE],
- "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY],
- "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES],
- "LC_PAPER", &c->locale[LOCALE_LC_PAPER],
- "LC_NAME", &c->locale[LOCALE_LC_NAME],
- "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS],
- "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE],
- "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT],
- "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
- NULL);
-
- if (r == -ENOENT) {
- int p;
-
- /* Fill in what we got passed from systemd. */
- for (p = 0; p < _LOCALE_MAX; p++) {
- assert(names[p]);
-
- r = free_and_strdup(&c->locale[p], empty_to_null(getenv(names[p])));
- if (r < 0)
- return r;
- }
-
- r = 0;
- }
-
- locale_simplify(c);
- return r;
-}
-
-static int vconsole_read_data(Context *c) {
- int r;
- context_free_vconsole(c);
-
- r = parse_env_file("/etc/vconsole.conf", NEWLINE,
- "KEYMAP", &c->vc_keymap,
- "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
- NULL);
-
- if (r < 0 && r != -ENOENT)
- return r;
-
- return 0;
-}
-
-static int x11_read_data(Context *c) {
- _cleanup_fclose_ FILE *f;
- char line[LINE_MAX];
- bool in_section = false;
- int r;
-
- context_free_x11(c);
-
- f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
- if (!f)
- return errno == ENOENT ? 0 : -errno;
-
- while (fgets(line, sizeof(line), f)) {
- char *l;
-
- char_array_0(line);
- l = strstrip(line);
-
- if (l[0] == 0 || l[0] == '#')
- continue;
-
- if (in_section && first_word(l, "Option")) {
- _cleanup_strv_free_ char **a = NULL;
-
- r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
- if (r < 0)
- return r;
-
- if (strv_length(a) == 3) {
- char **p = NULL;
-
- if (streq(a[1], "XkbLayout"))
- p = &c->x11_layout;
- else if (streq(a[1], "XkbModel"))
- p = &c->x11_model;
- else if (streq(a[1], "XkbVariant"))
- p = &c->x11_variant;
- else if (streq(a[1], "XkbOptions"))
- p = &c->x11_options;
-
- if (p) {
- free(*p);
- *p = a[2];
- a[2] = NULL;
- }
- }
-
- } else if (!in_section && first_word(l, "Section")) {
- _cleanup_strv_free_ char **a = NULL;
-
- r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
- if (r < 0)
- return -ENOMEM;
-
- if (strv_length(a) == 2 && streq(a[1], "InputClass"))
- in_section = true;
-
- } else if (in_section && first_word(l, "EndSection"))
- in_section = false;
- }
-
- return 0;
-}
-
-static int context_read_data(Context *c) {
- int r, q, p;
-
- r = locale_read_data(c);
- q = vconsole_read_data(c);
- p = x11_read_data(c);
-
- return r < 0 ? r : q < 0 ? q : p;
-}
-
-static int locale_write_data(Context *c, char ***settings) {
- int r, p;
- _cleanup_strv_free_ char **l = NULL;
-
- /* Set values will be returned as strv in *settings on success. */
-
- r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
- if (r < 0 && r != -ENOENT)
- return r;
-
- for (p = 0; p < _LOCALE_MAX; p++) {
- _cleanup_free_ char *t = NULL;
- char **u;
-
- assert(names[p]);
-
- if (isempty(c->locale[p])) {
- l = strv_env_unset(l, names[p]);
- continue;
- }
-
- if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
- return -ENOMEM;
-
- u = strv_env_set(l, t);
- if (!u)
- return -ENOMEM;
-
- strv_free(l);
- l = u;
- }
-
- if (strv_isempty(l)) {
- if (unlink("/etc/locale.conf") < 0)
- return errno == ENOENT ? 0 : -errno;
-
- return 0;
- }
-
- r = write_env_file_label("/etc/locale.conf", l);
- if (r < 0)
- return r;
-
- *settings = l;
- l = NULL;
- return 0;
-}
+static Hashmap *polkit_registry = NULL;
static int locale_update_system_manager(Context *c, sd_bus *bus) {
_cleanup_free_ char **l_unset = NULL;
@@ -327,30 +55,33 @@ static int locale_update_system_manager(Context *c, sd_bus *bus) {
assert(bus);
- l_unset = new0(char*, _LOCALE_MAX);
+ l_unset = new0(char*, _VARIABLE_LC_MAX);
if (!l_unset)
return -ENOMEM;
- l_set = new0(char*, _LOCALE_MAX);
+ l_set = new0(char*, _VARIABLE_LC_MAX);
if (!l_set)
return -ENOMEM;
- for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) {
- assert(names[p]);
+ for (p = 0, c_set = 0, c_unset = 0; p < _VARIABLE_LC_MAX; p++) {
+ const char *name;
+
+ name = locale_variable_to_string(p);
+ assert(name);
if (isempty(c->locale[p]))
- l_unset[c_set++] = (char*) names[p];
+ l_unset[c_set++] = (char*) name;
else {
char *s;
- if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0)
+ if (asprintf(&s, "%s=%s", name, c->locale[p]) < 0)
return -ENOMEM;
l_set[c_unset++] = s;
}
}
- assert(c_set + c_unset == _LOCALE_MAX);
+ assert(c_set + c_unset == _VARIABLE_LC_MAX);
r = sd_bus_message_new_method_call(bus, &m,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
@@ -374,124 +105,6 @@ static int locale_update_system_manager(Context *c, sd_bus *bus) {
return 0;
}
-static int vconsole_write_data(Context *c) {
- int r;
- _cleanup_strv_free_ char **l = NULL;
-
- r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
- if (r < 0 && r != -ENOENT)
- return r;
-
- if (isempty(c->vc_keymap))
- l = strv_env_unset(l, "KEYMAP");
- else {
- _cleanup_free_ char *s = NULL;
- char **u;
-
- s = strappend("KEYMAP=", c->vc_keymap);
- if (!s)
- return -ENOMEM;
-
- u = strv_env_set(l, s);
- if (!u)
- return -ENOMEM;
-
- strv_free(l);
- l = u;
- }
-
- if (isempty(c->vc_keymap_toggle))
- l = strv_env_unset(l, "KEYMAP_TOGGLE");
- else {
- _cleanup_free_ char *s = NULL;
- char **u;
-
- s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
- if (!s)
- return -ENOMEM;
-
- u = strv_env_set(l, s);
- if (!u)
- return -ENOMEM;
-
- strv_free(l);
- l = u;
- }
-
- if (strv_isempty(l)) {
- if (unlink("/etc/vconsole.conf") < 0)
- return errno == ENOENT ? 0 : -errno;
-
- return 0;
- }
-
- return write_env_file_label("/etc/vconsole.conf", l);
-}
-
-static int x11_write_data(Context *c) {
- _cleanup_fclose_ FILE *f = NULL;
- _cleanup_free_ char *temp_path = NULL;
- int r;
-
- if (isempty(c->x11_layout) &&
- isempty(c->x11_model) &&
- isempty(c->x11_variant) &&
- isempty(c->x11_options)) {
-
- if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
- return errno == ENOENT ? 0 : -errno;
-
- return 0;
- }
-
- mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
-
- r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
- if (r < 0)
- return r;
-
- fchmod(fileno(f), 0644);
-
- fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
- "# manually too freely.\n"
- "Section \"InputClass\"\n"
- " Identifier \"system-keyboard\"\n"
- " MatchIsKeyboard \"on\"\n", f);
-
- if (!isempty(c->x11_layout))
- fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
-
- if (!isempty(c->x11_model))
- fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
-
- if (!isempty(c->x11_variant))
- fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
-
- if (!isempty(c->x11_options))
- fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
-
- fputs("EndSection\n", f);
-
- r = fflush_and_check(f);
- if (r < 0)
- goto fail;
-
- if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
- r = -errno;
- goto fail;
- }
-
- return 0;
-
-fail:
- (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
-
- if (temp_path)
- (void) unlink(temp_path);
-
- return r;
-}
-
static int vconsole_reload(sd_bus *bus) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
@@ -512,337 +125,48 @@ static int vconsole_reload(sd_bus *bus) {
return r;
}
-static const char* strnulldash(const char *s) {
- return isempty(s) || streq(s, "-") ? NULL : s;
-}
-
-static int read_next_mapping(const char* filename,
- unsigned min_fields, unsigned max_fields,
- FILE *f, unsigned *n, char ***a) {
- assert(f);
- assert(n);
- assert(a);
-
- for (;;) {
- char line[LINE_MAX];
- char *l, **b;
- int r;
- size_t length;
-
- errno = 0;
- if (!fgets(line, sizeof(line), f)) {
-
- if (ferror(f))
- return errno > 0 ? -errno : -EIO;
-
- return 0;
- }
-
- (*n)++;
-
- l = strstrip(line);
- if (l[0] == 0 || l[0] == '#')
- continue;
-
- r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES);
- if (r < 0)
- return r;
-
- length = strv_length(b);
- if (length < min_fields || length > max_fields) {
- log_error("Invalid line %s:%u, ignoring.", filename, *n);
- strv_free(b);
- continue;
-
- }
-
- *a = b;
- return 1;
- }
-}
-
-static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
- bool modified = false;
-
- assert(bus);
-
- if (isempty(c->vc_keymap)) {
-
- modified =
- !isempty(c->x11_layout) ||
- !isempty(c->x11_model) ||
- !isempty(c->x11_variant) ||
- !isempty(c->x11_options);
-
- context_free_x11(c);
- } else {
- _cleanup_fclose_ FILE *f = NULL;
- unsigned n = 0;
-
- f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
- if (!f)
- return -errno;
-
- for (;;) {
- _cleanup_strv_free_ char **a = NULL;
- int r;
-
- r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
- if (r < 0)
- return r;
- if (r == 0)
- break;
-
- if (!streq(c->vc_keymap, a[0]))
- continue;
-
- if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
- !streq_ptr(c->x11_model, strnulldash(a[2])) ||
- !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
- !streq_ptr(c->x11_options, strnulldash(a[4]))) {
-
- if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
- free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
- free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
- free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
- return -ENOMEM;
-
- modified = true;
- }
-
- break;
- }
- }
-
- if (modified) {
- int r;
-
- r = x11_write_data(c);
- if (r < 0)
- return log_error_errno(r, "Failed to set X11 keyboard layout: %m");
-
- log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
- strempty(c->x11_layout),
- strempty(c->x11_model),
- strempty(c->x11_variant),
- strempty(c->x11_options));
-
- sd_bus_emit_properties_changed(bus,
- "/org/freedesktop/locale1",
- "org.freedesktop.locale1",
- "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
- } else
- log_debug("X11 keyboard layout was not modified.");
-
- return 0;
-}
-
-static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
- const char *dir;
- _cleanup_free_ char *n;
-
- if (x11_variant)
- n = strjoin(x11_layout, "-", x11_variant, NULL);
- else
- n = strdup(x11_layout);
- if (!n)
- return -ENOMEM;
-
- NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
- _cleanup_free_ char *p = NULL, *pz = NULL;
- bool uncompressed;
-
- p = strjoin(dir, "xkb/", n, ".map", NULL);
- pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
- if (!p || !pz)
- return -ENOMEM;
-
- uncompressed = access(p, F_OK) == 0;
- if (uncompressed || access(pz, F_OK) == 0) {
- log_debug("Found converted keymap %s at %s",
- n, uncompressed ? p : pz);
-
- *new_keymap = n;
- n = NULL;
- return 1;
- }
- }
-
- return 0;
-}
-
-static int find_legacy_keymap(Context *c, char **new_keymap) {
- _cleanup_fclose_ FILE *f;
- unsigned n = 0;
- unsigned best_matching = 0;
+static int vconsole_convert_to_x11_and_emit(Context *c, sd_bus *bus) {
int r;
- f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
- if (!f)
- return -errno;
-
- for (;;) {
- _cleanup_strv_free_ char **a = NULL;
- unsigned matching = 0;
-
- r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
- if (r < 0)
- return r;
- if (r == 0)
- break;
-
- /* Determine how well matching this entry is */
- if (streq_ptr(c->x11_layout, a[1]))
- /* If we got an exact match, this is best */
- matching = 10;
- else {
- /* We have multiple X layouts, look for an
- * entry that matches our key with everything
- * but the first layout stripped off. */
- if (startswith_comma(c->x11_layout, a[1]))
- matching = 5;
- else {
- char *x;
-
- /* If that didn't work, strip off the
- * other layouts from the entry, too */
- x = strndupa(a[1], strcspn(a[1], ","));
- if (startswith_comma(c->x11_layout, x))
- matching = 1;
- }
- }
-
- if (matching > 0) {
- if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
- matching++;
-
- if (streq_ptr(c->x11_variant, a[3])) {
- matching++;
-
- if (streq_ptr(c->x11_options, a[4]))
- matching++;
- }
- }
- }
-
- /* The best matching entry so far, then let's save that */
- if (matching >= MAX(best_matching, 1u)) {
- log_debug("Found legacy keymap %s with score %u",
- a[0], matching);
-
- if (matching > best_matching) {
- best_matching = matching;
-
- r = free_and_strdup(new_keymap, a[0]);
- if (r < 0)
- return r;
- }
- }
- }
-
- if (best_matching < 10 && c->x11_layout) {
- /* The best match is only the first part of the X11
- * keymap. Check if we have a converted map which
- * matches just the first layout.
- */
- char *l, *v = NULL, *converted;
-
- l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
- if (c->x11_variant)
- v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
- r = find_converted_keymap(l, v, &converted);
- if (r < 0)
- return r;
- if (r > 0) {
- free(*new_keymap);
- *new_keymap = converted;
- }
- }
-
- return 0;
-}
-
-static int find_language_fallback(const char *lang, char **language) {
- _cleanup_fclose_ FILE *f = NULL;
- unsigned n = 0;
-
- assert(language);
-
- f = fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP, "re");
- if (!f)
- return -errno;
+ assert(bus);
- for (;;) {
- _cleanup_strv_free_ char **a = NULL;
- int r;
+ r = vconsole_convert_to_x11(c);
+ if (r <= 0)
+ return r;
- r = read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP, 2, 2, f, &n, &a);
- if (r <= 0)
- return r;
+ /* modified */
+ r = x11_write_data(c);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write X11 keyboard layout: %m");
- if (streq(lang, a[0])) {
- assert(strv_length(a) == 2);
- *language = a[1];
- a[1] = NULL;
- return 1;
- }
- }
+ sd_bus_emit_properties_changed(bus,
+ "/org/freedesktop/locale1",
+ "org.freedesktop.locale1",
+ "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
- assert_not_reached("should not be here");
+ return 1;
}
-static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
- bool modified = false;
+static int x11_convert_to_vconsole_and_emit(Context *c, sd_bus *bus) {
int r;
assert(bus);
- if (isempty(c->x11_layout)) {
-
- modified =
- !isempty(c->vc_keymap) ||
- !isempty(c->vc_keymap_toggle);
-
- context_free_x11(c);
- } else {
- char *new_keymap = NULL;
-
- r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
- if (r < 0)
- return r;
- else if (r == 0) {
- r = find_legacy_keymap(c, &new_keymap);
- if (r < 0)
- return r;
- }
-
- if (!streq_ptr(c->vc_keymap, new_keymap)) {
- free(c->vc_keymap);
- c->vc_keymap = new_keymap;
- c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
- modified = true;
- } else
- free(new_keymap);
- }
-
- if (modified) {
- r = vconsole_write_data(c);
- if (r < 0)
- log_error_errno(r, "Failed to set virtual console keymap: %m");
-
- log_info("Changed virtual console keymap to '%s' toggle '%s'",
- strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
+ r = x11_convert_to_vconsole(c);
+ if (r <= 0)
+ return r;
- sd_bus_emit_properties_changed(bus,
- "/org/freedesktop/locale1",
- "org.freedesktop.locale1",
- "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
+ /* modified */
+ r = vconsole_write_data(c);
+ if (r < 0)
+ log_error_errno(r, "Failed to save virtual console keymap: %m");
- return vconsole_reload(bus);
- } else
- log_debug("Virtual console keymap was not modified.");
+ sd_bus_emit_properties_changed(bus,
+ "/org/freedesktop/locale1",
+ "org.freedesktop.locale1",
+ "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
- return 0;
+ return vconsole_reload(bus);
}
static int property_get_locale(
@@ -858,17 +182,21 @@ static int property_get_locale(
_cleanup_strv_free_ char **l = NULL;
int p, q;
- l = new0(char*, _LOCALE_MAX+1);
+ l = new0(char*, _VARIABLE_LC_MAX+1);
if (!l)
return -ENOMEM;
- for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
+ for (p = 0, q = 0; p < _VARIABLE_LC_MAX; p++) {
char *t;
+ const char *name;
+
+ name = locale_variable_to_string(p);
+ assert(name);
if (isempty(c->locale[p]))
continue;
- if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
+ if (asprintf(&t, "%s=%s", name, c->locale[p]) < 0)
return -ENOMEM;
l[q++] = t;
@@ -884,7 +212,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
const char *lang = NULL;
int interactive;
bool modified = false;
- bool have[_LOCALE_MAX] = {};
+ bool have[_VARIABLE_LC_MAX] = {};
int p;
int r;
@@ -903,17 +231,21 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
STRV_FOREACH(i, l) {
bool valid = false;
- for (p = 0; p < _LOCALE_MAX; p++) {
+ for (p = 0; p < _VARIABLE_LC_MAX; p++) {
size_t k;
+ const char *name;
+
+ name = locale_variable_to_string(p);
+ assert(name);
- k = strlen(names[p]);
- if (startswith(*i, names[p]) &&
+ k = strlen(name);
+ if (startswith(*i, name) &&
(*i)[k] == '=' &&
locale_is_valid((*i) + k + 1)) {
valid = true;
have[p] = true;
- if (p == LOCALE_LANG)
+ if (p == VARIABLE_LANG)
lang = (*i) + k + 1;
if (!streq_ptr(*i + k + 1, c->locale[p]))
@@ -929,7 +261,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
/* If LANG was specified, but not LANGUAGE, check if we should
* set it based on the language fallback table. */
- if (have[LOCALE_LANG] && !have[LOCALE_LANGUAGE]) {
+ if (have[VARIABLE_LANG] && !have[VARIABLE_LANGUAGE]) {
_cleanup_free_ char *language = NULL;
assert(lang);
@@ -937,12 +269,12 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
(void) find_language_fallback(lang, &language);
if (language) {
log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language);
- if (!streq_ptr(language, c->locale[LOCALE_LANGUAGE])) {
+ if (!streq_ptr(language, c->locale[VARIABLE_LANGUAGE])) {
r = strv_extendf(&l, "LANGUAGE=%s", language);
if (r < 0)
return r;
- have[LOCALE_LANGUAGE] = true;
+ have[VARIABLE_LANGUAGE] = true;
modified = true;
}
}
@@ -950,7 +282,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
/* Check whether a variable is unset */
if (!modified)
- for (p = 0; p < _LOCALE_MAX; p++)
+ for (p = 0; p < _VARIABLE_LC_MAX; p++)
if (!isempty(c->locale[p]) && !have[p]) {
modified = true;
break;
@@ -966,7 +298,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
NULL,
interactive,
UID_INVALID,
- &c->polkit_registry,
+ &polkit_registry,
error);
if (r < 0)
return r;
@@ -974,11 +306,15 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
STRV_FOREACH(i, l)
- for (p = 0; p < _LOCALE_MAX; p++) {
+ for (p = 0; p < _VARIABLE_LC_MAX; p++) {
size_t k;
+ const char *name;
- k = strlen(names[p]);
- if (startswith(*i, names[p]) && (*i)[k] == '=') {
+ name = locale_variable_to_string(p);
+ assert(name);
+
+ k = strlen(name);
+ if (startswith(*i, name) && (*i)[k] == '=') {
r = free_and_strdup(&c->locale[p], *i + k + 1);
if (r < 0)
return r;
@@ -986,7 +322,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er
}
}
- for (p = 0; p < _LOCALE_MAX; p++) {
+ for (p = 0; p < _VARIABLE_LC_MAX; p++) {
if (have[p])
continue;
@@ -1053,7 +389,7 @@ static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_erro
NULL,
interactive,
UID_INVALID,
- &c->polkit_registry,
+ &polkit_registry,
error);
if (r < 0)
return r;
@@ -1084,7 +420,7 @@ static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_erro
"VConsoleKeymap", "VConsoleKeymapToggle", NULL);
if (convert) {
- r = vconsole_convert_to_x11(c, sd_bus_message_get_bus(m));
+ r = vconsole_convert_to_x11_and_emit(c, sd_bus_message_get_bus(m));
if (r < 0)
log_error_errno(r, "Failed to convert keymap data: %m");
}
@@ -1229,7 +565,7 @@ static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_err
NULL,
interactive,
UID_INVALID,
- &c->polkit_registry,
+ &polkit_registry,
error);
if (r < 0)
return r;
@@ -1272,7 +608,7 @@ static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_err
"X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
if (convert) {
- r = x11_convert_to_vconsole(c, sd_bus_message_get_bus(m));
+ r = x11_convert_to_vconsole_and_emit(c, sd_bus_message_get_bus(m));
if (r < 0)
log_error_errno(r, "Failed to convert keymap data: %m");
}
@@ -1364,11 +700,11 @@ int main(int argc, char *argv[]) {
}
r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
- if (r < 0) {
+ if (r < 0)
log_error_errno(r, "Failed to run event loop: %m");
- goto finish;
- }
finish:
+ bus_verify_polkit_async_registry_free(polkit_registry);
+
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
diff --git a/src/locale/test-keymap-util.c b/src/locale/test-keymap-util.c
new file mode 100644
index 0000000000..2adda3da2b
--- /dev/null
+++ b/src/locale/test-keymap-util.c
@@ -0,0 +1,220 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "alloc-util.h"
+#include "keymap-util.h"
+#include "log.h"
+#include "string-util.h"
+
+static void test_find_language_fallback(void) {
+ _cleanup_free_ char *ans = NULL, *ans2 = NULL;
+
+ log_info("/*** %s ***/", __func__);
+
+ assert_se(find_language_fallback("foobar", &ans) == 0);
+ assert_se(ans == NULL);
+
+ assert_se(find_language_fallback("csb", &ans) == 0);
+ assert_se(ans == NULL);
+
+ assert_se(find_language_fallback("csb_PL", &ans) == 1);
+ assert_se(streq(ans, "csb:pl"));
+
+ assert_se(find_language_fallback("szl_PL", &ans2) == 1);
+ assert_se(streq(ans2, "szl:pl"));
+}
+
+static void test_find_converted_keymap(void) {
+ _cleanup_free_ char *ans = NULL, *ans2 = NULL;
+ int r;
+
+ log_info("/*** %s ***/", __func__);
+
+ assert_se(find_converted_keymap("pl", "foobar", &ans) == 0);
+ assert_se(ans == NULL);
+
+ r = find_converted_keymap("pl", NULL, &ans);
+ if (r == 0) {
+ log_info("Skipping rest of %s: keymaps are not installed", __func__);
+ return;
+ }
+
+ assert_se(r == 1);
+ assert_se(streq(ans, "pl"));
+
+ assert_se(find_converted_keymap("pl", "dvorak", &ans2) == 1);
+ assert_se(streq(ans2, "pl-dvorak"));
+}
+
+static void test_find_legacy_keymap(void) {
+ Context c = {};
+ _cleanup_free_ char *ans = NULL, *ans2 = NULL;
+
+ log_info("/*** %s ***/", __func__);
+
+ c.x11_layout = (char*) "foobar";
+ assert_se(find_legacy_keymap(&c, &ans) == 0);
+ assert_se(ans == NULL);
+
+ c.x11_layout = (char*) "pl";
+ assert_se(find_legacy_keymap(&c, &ans) == 1);
+ assert_se(streq(ans, "pl2"));
+
+ c.x11_layout = (char*) "pl,ru";
+ assert_se(find_legacy_keymap(&c, &ans2) == 1);
+ assert_se(streq(ans, "pl2"));
+}
+
+static void test_vconsole_convert_to_x11(void) {
+ _cleanup_(context_free) Context c = {};
+
+ log_info("/*** %s ***/", __func__);
+
+ log_info("/* test emptying first (:) */");
+ assert_se(free_and_strdup(&c.x11_layout, "foo") >= 0);
+ assert_se(free_and_strdup(&c.x11_variant, "bar") >= 0);
+ assert_se(vconsole_convert_to_x11(&c) == 1);
+ assert_se(c.x11_layout == NULL);
+ assert_se(c.x11_variant == NULL);
+
+ log_info("/* test emptying second (:) */");
+
+ assert_se(vconsole_convert_to_x11(&c) == 0);
+ assert_se(c.x11_layout == NULL);
+ assert_se(c.x11_variant == NULL);
+
+ log_info("/* test without variant, new mapping (es:) */");
+ assert_se(free_and_strdup(&c.vc_keymap, "es") >= 0);
+
+ assert_se(vconsole_convert_to_x11(&c) == 1);
+ assert_se(streq(c.x11_layout, "es"));
+ assert_se(c.x11_variant == NULL);
+
+ log_info("/* test with known variant, new mapping (es:dvorak) */");
+ assert_se(free_and_strdup(&c.vc_keymap, "es-dvorak") >= 0);
+
+ assert_se(vconsole_convert_to_x11(&c) == 0); // FIXME
+ assert_se(streq(c.x11_layout, "es"));
+ assert_se(c.x11_variant == NULL); // FIXME: "dvorak"
+
+ log_info("/* test with old mapping (fr:latin9) */");
+ assert_se(free_and_strdup(&c.vc_keymap, "fr-latin9") >= 0);
+
+ assert_se(vconsole_convert_to_x11(&c) == 1);
+ assert_se(streq(c.x11_layout, "fr"));
+ assert_se(streq(c.x11_variant, "latin9"));
+
+ log_info("/* test with a compound mapping (ru,us) */");
+ assert_se(free_and_strdup(&c.vc_keymap, "ru") >= 0);
+
+ assert_se(vconsole_convert_to_x11(&c) == 1);
+ assert_se(streq(c.x11_layout, "ru,us"));
+ assert_se(c.x11_variant == NULL);
+
+ log_info("/* test with a simple mapping (us) */");
+ assert_se(free_and_strdup(&c.vc_keymap, "us") >= 0);
+
+ assert_se(vconsole_convert_to_x11(&c) == 1);
+ assert_se(streq(c.x11_layout, "us"));
+ assert_se(c.x11_variant == NULL);
+}
+
+static void test_x11_convert_to_vconsole(void) {
+ _cleanup_(context_free) Context c = {};
+ int r;
+
+ log_info("/*** %s ***/", __func__);
+
+ log_info("/* test emptying first (:) */");
+ assert_se(free_and_strdup(&c.vc_keymap, "foobar") >= 0);
+ assert_se(x11_convert_to_vconsole(&c) == 1);
+ assert_se(c.vc_keymap == NULL);
+
+ log_info("/* test emptying second (:) */");
+
+ assert_se(x11_convert_to_vconsole(&c) == 0);
+ assert_se(c.vc_keymap == NULL);
+
+ log_info("/* test without variant, new mapping (es:) */");
+ assert_se(free_and_strdup(&c.x11_layout, "es") >= 0);
+
+ assert_se(x11_convert_to_vconsole(&c) == 1);
+ assert_se(streq(c.vc_keymap, "es"));
+
+ log_info("/* test with unknown variant, new mapping (es:foobar) */");
+ assert_se(free_and_strdup(&c.x11_variant, "foobar") >= 0);
+
+ assert_se(x11_convert_to_vconsole(&c) == 0);
+ assert_se(streq(c.vc_keymap, "es"));
+
+ log_info("/* test with known variant, new mapping (es:dvorak) */");
+ assert_se(free_and_strdup(&c.x11_variant, "dvorak") >= 0);
+
+ r = x11_convert_to_vconsole(&c);
+ if (r == 0) {
+ log_info("Skipping rest of %s: keymaps are not installed", __func__);
+ return;
+ }
+
+ assert_se(r == 1);
+ assert_se(streq(c.vc_keymap, "es-dvorak"));
+
+ log_info("/* test with old mapping (fr:latin9) */");
+ assert_se(free_and_strdup(&c.x11_layout, "fr") >= 0);
+ assert_se(free_and_strdup(&c.x11_variant, "latin9") >= 0);
+
+ assert_se(x11_convert_to_vconsole(&c) == 1);
+ assert_se(streq(c.vc_keymap, "fr-latin9"));
+
+ log_info("/* test with a compound mapping (us,ru:) */");
+ assert_se(free_and_strdup(&c.x11_layout, "us,ru") >= 0);
+ assert_se(free_and_strdup(&c.x11_variant, NULL) >= 0);
+
+ assert_se(x11_convert_to_vconsole(&c) == 1);
+ assert_se(streq(c.vc_keymap, "us"));
+
+ log_info("/* test with a compound mapping (ru,us:) */");
+ assert_se(free_and_strdup(&c.x11_layout, "ru,us") >= 0);
+ assert_se(free_and_strdup(&c.x11_variant, NULL) >= 0);
+
+ assert_se(x11_convert_to_vconsole(&c) == 1);
+ assert_se(streq(c.vc_keymap, "ru"));
+
+ /* https://bugzilla.redhat.com/show_bug.cgi?id=1333998 */
+ log_info("/* test with a simple new mapping (ru:) */");
+ assert_se(free_and_strdup(&c.x11_layout, "ru") >= 0);
+ assert_se(free_and_strdup(&c.x11_variant, NULL) >= 0);
+
+ assert_se(x11_convert_to_vconsole(&c) == 0);
+ assert_se(streq(c.vc_keymap, "ru"));
+}
+
+int main(int argc, char **argv) {
+ log_set_max_level(LOG_DEBUG);
+ log_parse_environment();
+
+ test_find_language_fallback();
+ test_find_converted_keymap();
+ test_find_legacy_keymap();
+
+ test_vconsole_convert_to_x11();
+ test_x11_convert_to_vconsole();
+
+ return 0;
+}
diff --git a/src/login/loginctl.c b/src/login/loginctl.c
index 1c75565636..0fc33cf541 100644
--- a/src/login/loginctl.c
+++ b/src/login/loginctl.c
@@ -290,7 +290,7 @@ typedef struct SessionStatusInfo {
char *seat;
char *tty;
char *display;
- bool remote;
+ int remote;
char *remote_host;
char *remote_user;
char *service;
@@ -304,7 +304,7 @@ typedef struct SessionStatusInfo {
typedef struct UserStatusInfo {
uid_t uid;
- bool linger;
+ int linger;
char *name;
struct dual_timestamp timestamp;
char *state;
@@ -1554,7 +1554,7 @@ static int loginctl_main(int argc, char *argv[], sd_bus *bus) {
}
int main(int argc, char *argv[]) {
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ sd_bus *bus = NULL;
int r;
setlocale(LC_ALL, "");
@@ -1576,6 +1576,8 @@ int main(int argc, char *argv[]) {
r = loginctl_main(argc, argv, bus);
finish:
+ sd_bus_flush_close_unref(bus);
+
pager_close();
polkit_agent_close();
diff --git a/src/login/logind-action.c b/src/login/logind-action.c
index 9a8089f97c..8ef48dbaa1 100644
--- a/src/login/logind-action.c
+++ b/src/login/logind-action.c
@@ -85,7 +85,7 @@ int manager_handle_action(
}
/* If the key handling is inhibited, don't do anything */
- if (inhibit_key > 0) {
+ if (!ignore_inhibited && inhibit_key > 0) {
if (manager_is_inhibited(m, inhibit_key, INHIBIT_BLOCK, NULL, true, false, 0, NULL)) {
log_debug("Refusing operation, %s is inhibited.", inhibit_what_to_string(inhibit_key));
return 0;
@@ -124,7 +124,7 @@ int manager_handle_action(
return -EALREADY;
}
- inhibit_operation = handle == HANDLE_SUSPEND || handle == HANDLE_HIBERNATE || handle == HANDLE_HYBRID_SLEEP ? INHIBIT_SLEEP : INHIBIT_SHUTDOWN;
+ inhibit_operation = IN_SET(handle, HANDLE_SUSPEND, HANDLE_HIBERNATE, HANDLE_HYBRID_SLEEP) ? INHIBIT_SLEEP : INHIBIT_SHUTDOWN;
/* If the actual operation is inhibited, warn and fail */
if (!ignore_inhibited &&
diff --git a/src/login/logind-core.c b/src/login/logind-core.c
index cbf8d757fe..eff5a4a36f 100644
--- a/src/login/logind-core.c
+++ b/src/login/logind-core.c
@@ -496,7 +496,7 @@ static int manager_count_external_displays(Manager *m) {
continue;
/* Ignore internal displays: the type is encoded in
- * the sysfs name, as the second dash seperated item
+ * the sysfs name, as the second dash separated item
* (the first is the card name, the last the connector
* number). We implement a whitelist of external
* displays here, rather than a whitelist, to ensure
diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf
index 6bd08adc05..0b6a5f3cf4 100644
--- a/src/login/logind-gperf.gperf
+++ b/src/login/logind-gperf.gperf
@@ -36,4 +36,4 @@ Login.RuntimeDirectorySize, config_parse_tmpfs_size, 0, offsetof(Manag
Login.RemoveIPC, config_parse_bool, 0, offsetof(Manager, remove_ipc)
Login.InhibitorsMax, config_parse_uint64, 0, offsetof(Manager, inhibitors_max)
Login.SessionsMax, config_parse_uint64, 0, offsetof(Manager, sessions_max)
-Login.UserTasksMax, config_parse_uint64, 0, offsetof(Manager, user_tasks_max)
+Login.UserTasksMax, config_parse_user_tasks_max,0, offsetof(Manager, user_tasks_max)
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index 1e0666884a..b6da237397 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -603,7 +603,6 @@ int session_start(Session *s) {
static int session_stop_scope(Session *s, bool force) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- char *job = NULL;
int r;
assert(s);
@@ -611,22 +610,25 @@ static int session_stop_scope(Session *s, bool force) {
if (!s->scope)
return 0;
+ /* Let's always abandon the scope first. This tells systemd that we are not interested anymore, and everything
+ * that is left in in the scope is "left-over". Informing systemd about this has the benefit that it will log
+ * when killing any processes left after this point. */
+ r = manager_abandon_scope(s->manager, s->scope, &error);
+ if (r < 0)
+ log_warning_errno(r, "Failed to abandon session scope, ignoring: %s", bus_error_message(&error, r));
+
+ /* Optionally, let's kill everything that's left now. */
if (force || manager_shall_kill(s->manager, s->user->name)) {
+ char *job = NULL;
+
r = manager_stop_unit(s->manager, s->scope, &error, &job);
- if (r < 0) {
- log_error("Failed to stop session scope: %s", bus_error_message(&error, r));
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to stop session scope: %s", bus_error_message(&error, r));
free(s->scope_job);
s->scope_job = job;
- } else {
- r = manager_abandon_scope(s->manager, s->scope, &error);
- if (r < 0) {
- log_error("Failed to abandon session scope: %s", bus_error_message(&error, r));
- return r;
- }
- }
+ } else
+ s->scope_job = mfree(s->scope_job);
return 0;
}
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
index a826321bf0..63363035e7 100644
--- a/src/login/logind-user.c
+++ b/src/login/logind-user.c
@@ -311,8 +311,7 @@ int user_load(User *u) {
if (r == -ENOENT)
return 0;
- log_error_errno(r, "Failed to read %s: %m", u->state_file);
- return r;
+ return log_error_errno(r, "Failed to read %s: %m", u->state_file);
}
if (display)
@@ -843,7 +842,6 @@ int config_parse_tmpfs_size(
void *userdata) {
size_t *sz = data;
- const char *e;
int r;
assert(filename);
@@ -851,35 +849,68 @@ int config_parse_tmpfs_size(
assert(rvalue);
assert(data);
- e = endswith(rvalue, "%");
- if (e) {
- unsigned long ul;
- char *f;
+ /* First, try to parse as percentage */
+ r = parse_percent(rvalue);
+ if (r > 0 && r < 100)
+ *sz = physical_memory_scale(r, 100U);
+ else {
+ uint64_t k;
- errno = 0;
- ul = strtoul(rvalue, &f, 10);
- if (errno > 0 || f != e) {
- log_syntax(unit, LOG_ERR, filename, line, errno, "Failed to parse percentage value, ignoring: %s", rvalue);
- return 0;
- }
+ /* If the passed argument was not a percentage, or out of range, parse as byte size */
- if (ul <= 0 || ul >= 100) {
- log_syntax(unit, LOG_ERR, filename, line, 0, "Percentage value out of range, ignoring: %s", rvalue);
+ r = parse_size(rvalue, 1024, &k);
+ if (r < 0 || k <= 0 || (uint64_t) (size_t) k != k) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue);
return 0;
}
- *sz = PAGE_ALIGN((size_t) ((physical_memory() * (uint64_t) ul) / (uint64_t) 100));
- } else {
- uint64_t k;
+ *sz = PAGE_ALIGN((size_t) k);
+ }
- r = parse_size(rvalue, 1024, &k);
- if (r < 0 || (uint64_t) (size_t) k != k) {
- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue);
+ return 0;
+}
+
+int config_parse_user_tasks_max(
+ const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ uint64_t *m = data;
+ uint64_t k;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ /* First, try to parse as percentage */
+ r = parse_percent(rvalue);
+ if (r >= 0)
+ k = system_tasks_max_scale(r, 100U);
+ else {
+
+ /* If the passed argument was not a percentage, or out of range, parse as byte size */
+
+ r = safe_atou64(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse tasks maximum, ignoring: %s", rvalue);
return 0;
}
+ }
- *sz = PAGE_ALIGN((size_t) k);
+ if (k <= 0 || k >= UINT64_MAX) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Tasks maximum out of range, ignoring: %s", rvalue);
+ return 0;
}
+ *m = k;
return 0;
}
diff --git a/src/login/logind.c b/src/login/logind.c
index caf149cfb7..5ce36d28c7 100644
--- a/src/login/logind.c
+++ b/src/login/logind.c
@@ -61,8 +61,8 @@ static void manager_reset_config(Manager *m) {
m->idle_action_usec = 30 * USEC_PER_MINUTE;
m->idle_action = HANDLE_IGNORE;
- m->runtime_dir_size = PAGE_ALIGN((size_t) (physical_memory() / 10)); /* 10% */
- m->user_tasks_max = 12288;
+ m->runtime_dir_size = physical_memory_scale(10U, 100U); /* 10% */
+ m->user_tasks_max = system_tasks_max_scale(33U, 100U); /* 33% */
m->sessions_max = 8192;
m->inhibitors_max = 8192;
diff --git a/src/login/logind.conf.in b/src/login/logind.conf.in
index 32c0844cb6..6f720b7708 100644
--- a/src/login/logind.conf.in
+++ b/src/login/logind.conf.in
@@ -34,4 +34,4 @@
#RemoveIPC=yes
#InhibitorsMax=8192
#SessionsMax=8192
-#UserTasksMax=12288
+#UserTasksMax=33%
diff --git a/src/login/logind.h b/src/login/logind.h
index 90431eb4b0..086fa1eeb5 100644
--- a/src/login/logind.h
+++ b/src/login/logind.h
@@ -187,6 +187,7 @@ const struct ConfigPerfItem* logind_gperf_lookup(const char *key, unsigned lengt
int manager_set_lid_switch_ignore(Manager *m, usec_t until);
int config_parse_tmpfs_size(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_user_tasks_max(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int manager_get_session_from_creds(Manager *m, sd_bus_message *message, const char *name, sd_bus_error *error, Session **ret);
int manager_get_user_from_creds(Manager *m, sd_bus_message *message, uid_t uid, sd_bus_error *error, User **ret);
diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c
index 98dc201340..4f023640f6 100644
--- a/src/login/pam_systemd.c
+++ b/src/login/pam_systemd.c
@@ -182,25 +182,20 @@ static int export_legacy_dbus_address(
_cleanup_free_ char *s = NULL;
int r = PAM_BUF_ERR;
- if (is_kdbus_available()) {
- if (asprintf(&s, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, runtime) < 0)
- goto error;
- } else {
- /* FIXME: We *really* should move the access() check into the
- * daemons that spawn dbus-daemon, instead of forcing
- * DBUS_SESSION_BUS_ADDRESS= here. */
-
- s = strjoin(runtime, "/bus", NULL);
- if (!s)
- goto error;
-
- if (access(s, F_OK) < 0)
- return PAM_SUCCESS;
+ /* FIXME: We *really* should move the access() check into the
+ * daemons that spawn dbus-daemon, instead of forcing
+ * DBUS_SESSION_BUS_ADDRESS= here. */
- s = mfree(s);
- if (asprintf(&s, UNIX_USER_BUS_ADDRESS_FMT, runtime) < 0)
- goto error;
- }
+ s = strjoin(runtime, "/bus", NULL);
+ if (!s)
+ goto error;
+
+ if (access(s, F_OK) < 0)
+ return PAM_SUCCESS;
+
+ s = mfree(s);
+ if (asprintf(&s, UNIX_USER_BUS_ADDRESS_FMT, runtime) < 0)
+ goto error;
r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
if (r != PAM_SUCCESS)
diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c
index 1d55fa04af..cc9b1b38fe 100644
--- a/src/machine-id-setup/machine-id-setup-main.c
+++ b/src/machine-id-setup/machine-id-setup-main.c
@@ -29,6 +29,7 @@
static char *arg_root = NULL;
static bool arg_commit = false;
+static bool arg_print = false;
static void help(void) {
printf("%s [OPTIONS...]\n\n"
@@ -37,6 +38,7 @@ static void help(void) {
" --version Show package version\n"
" --root=ROOT Filesystem root\n"
" --commit Commit transient ID\n"
+ " --print Print used machine ID\n"
, program_invocation_short_name);
}
@@ -46,6 +48,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_VERSION = 0x100,
ARG_ROOT,
ARG_COMMIT,
+ ARG_PRINT,
};
static const struct option options[] = {
@@ -53,6 +56,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "version", no_argument, NULL, ARG_VERSION },
{ "root", required_argument, NULL, ARG_ROOT },
{ "commit", no_argument, NULL, ARG_COMMIT },
+ { "print", no_argument, NULL, ARG_PRINT },
{}
};
@@ -82,6 +86,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_commit = true;
break;
+ case ARG_PRINT:
+ arg_print = true;
+ break;
+
case '?':
return -EINVAL;
@@ -98,6 +106,8 @@ static int parse_argv(int argc, char *argv[]) {
}
int main(int argc, char *argv[]) {
+ char buf[SD_ID128_STRING_MAX];
+ sd_id128_t id;
int r;
log_parse_environment();
@@ -107,10 +117,24 @@ int main(int argc, char *argv[]) {
if (r <= 0)
goto finish;
- if (arg_commit)
+ if (arg_commit) {
r = machine_id_commit(arg_root);
- else
- r = machine_id_setup(arg_root, SD_ID128_NULL);
+ if (r < 0)
+ goto finish;
+
+ r = sd_id128_get_machine(&id);
+ if (r < 0) {
+ log_error_errno(r, "Failed to read machine ID back: %m");
+ goto finish;
+ }
+ } else {
+ r = machine_id_setup(arg_root, SD_ID128_NULL, &id);
+ if (r < 0)
+ goto finish;
+ }
+
+ if (arg_print)
+ puts(sd_id128_to_string(id, buf));
finish:
free(arg_root);
diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c
index 0eed9b81bb..867bbc467b 100644
--- a/src/machine/image-dbus.c
+++ b/src/machine/image-dbus.c
@@ -81,7 +81,7 @@ int bus_image_method_remove(
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
- r = operation_new(m, NULL, child, message, errno_pipe_fd[0]);
+ r = operation_new(m, NULL, child, message, errno_pipe_fd[0], NULL);
if (r < 0) {
(void) sigkill_wait(child);
return r;
@@ -193,7 +193,7 @@ int bus_image_method_clone(
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
- r = operation_new(m, NULL, child, message, errno_pipe_fd[0]);
+ r = operation_new(m, NULL, child, message, errno_pipe_fd[0], NULL);
if (r < 0) {
(void) sigkill_wait(child);
return r;
diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c
index de5d98f23e..ba7ac04b56 100644
--- a/src/machine/machine-dbus.c
+++ b/src/machine/machine-dbus.c
@@ -1200,8 +1200,6 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro
child_fail:
(void) write(errno_pipe_fd[1], &r, sizeof(r));
- errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
-
_exit(EXIT_FAILURE);
}
@@ -1209,7 +1207,7 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro
/* Copying might take a while, hence install a watch on the child, and return */
- r = operation_new(m->manager, m, child, message, errno_pipe_fd[0]);
+ r = operation_new(m->manager, m, child, message, errno_pipe_fd[0], NULL);
if (r < 0) {
(void) sigkill_wait(child);
return r;
diff --git a/src/machine/machine.c b/src/machine/machine.c
index c1fae57084..dd046d6563 100644
--- a/src/machine/machine.c
+++ b/src/machine/machine.c
@@ -181,7 +181,7 @@ int machine_save(Machine *m) {
fprintf(f, "ROOT=%s\n", escaped);
}
- if (!sd_id128_equal(m->id, SD_ID128_NULL))
+ if (!sd_id128_is_null(m->id))
fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id));
if (m->leader != 0)
diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index 8e4ffa9a39..c78ca7ad76 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -32,6 +32,7 @@
#include "sd-bus.h"
#include "alloc-util.h"
+#include "bus-common-errors.h"
#include "bus-error.h"
#include "bus-unit-util.h"
#include "bus-util.h"
@@ -184,7 +185,7 @@ static int list_machines(int argc, char *argv[], void *userdata) {
qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info);
- if (arg_legend)
+ if (arg_legend && n_machines > 0)
printf("%-*s %-*s %-*s\n",
(int) max_name, "MACHINE",
(int) max_class, "CLASS",
@@ -196,8 +197,10 @@ static int list_machines(int argc, char *argv[], void *userdata) {
(int) max_class, machines[j].class,
(int) max_service, machines[j].service);
- if (arg_legend)
+ if (arg_legend && n_machines > 0)
printf("\n%zu machines listed.\n", n_machines);
+ else
+ printf("No machines.\n");
return 0;
}
@@ -304,7 +307,7 @@ static int list_images(int argc, char *argv[], void *userdata) {
qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info);
- if (arg_legend)
+ if (arg_legend && n_images > 0)
printf("%-*s %-*s %-3s %-*s %-*s %-*s\n",
(int) max_name, "NAME",
(int) max_type, "TYPE",
@@ -325,8 +328,10 @@ static int list_images(int argc, char *argv[], void *userdata) {
(int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime)));
}
- if (arg_legend)
+ if (arg_legend && n_images > 0)
printf("\n%zu images listed.\n", n_images);
+ else
+ printf("No images.\n");
return 0;
}
@@ -527,7 +532,7 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
fputs(strna(i->name), stdout);
- if (!sd_id128_equal(i->id, SD_ID128_NULL))
+ if (!sd_id128_is_null(i->id))
printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id));
else
putchar('\n');
@@ -1523,8 +1528,33 @@ static int read_only_image(int argc, char *argv[], void *userdata) {
return 0;
}
+static int image_exists(sd_bus *bus, const char *name) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(bus);
+ assert(name);
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.machine1",
+ "/org/freedesktop/machine1",
+ "org.freedesktop.machine1.Manager",
+ "GetImage",
+ &error,
+ NULL,
+ "s", name);
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_IMAGE))
+ return 0;
+
+ return log_error_errno(r, "Failed to check whether image %s exists: %s", name, bus_error_message(&error, -r));
+ }
+
+ return 1;
+}
+
static int make_service_name(const char *name, char **ret) {
- _cleanup_free_ char *e = NULL;
int r;
assert(name);
@@ -1535,11 +1565,7 @@ static int make_service_name(const char *name, char **ret) {
return -EINVAL;
}
- e = unit_name_escape(name);
- if (!e)
- return log_oom();
-
- r = unit_name_build("systemd-nspawn", e, ".service", ret);
+ r = unit_name_build("systemd-nspawn", name, ".service", ret);
if (r < 0)
return log_error_errno(r, "Failed to build unit name: %m");
@@ -1569,6 +1595,14 @@ static int start_machine(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
+ r = image_exists(bus, argv[i]);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ log_error("Machine image '%s' does not exist.", argv[1]);
+ return -ENXIO;
+ }
+
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
@@ -1636,6 +1670,14 @@ static int enable_machine(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
+ r = image_exists(bus, argv[i]);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ log_error("Machine image '%s' does not exist.", argv[1]);
+ return -ENXIO;
+ }
+
r = sd_bus_message_append(m, "s", unit);
if (r < 0)
return bus_log_create_error(r);
@@ -2276,7 +2318,7 @@ static int list_transfers(int argc, char *argv[], void *userdata) {
qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info);
- if (arg_legend)
+ if (arg_legend && n_transfers > 0)
printf("%-*s %-*s %-*s %-*s %-*s\n",
(int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID",
(int) 7, "PERCENT",
@@ -2292,8 +2334,10 @@ static int list_transfers(int argc, char *argv[], void *userdata) {
(int) max_local, transfers[j].local,
(int) max_remote, transfers[j].remote);
- if (arg_legend)
+ if (arg_legend && n_transfers > 0)
printf("\n%zu transfers listed.\n", n_transfers);
+ else
+ printf("No transfers.\n");
return 0;
}
@@ -2379,7 +2423,7 @@ static int set_limit(int argc, char *argv[], void *userdata) {
}
static int clean_images(int argc, char *argv[], void *userdata) {
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
uint64_t usage, total = 0;
char fb[FORMAT_BYTES_MAX];
@@ -2388,15 +2432,22 @@ static int clean_images(int argc, char *argv[], void *userdata) {
unsigned c = 0;
int r;
- r = sd_bus_call_method(
+ r = sd_bus_message_new_method_call(
bus,
+ &m,
"org.freedesktop.machine1",
"/org/freedesktop/machine1",
"org.freedesktop.machine1.Manager",
- "CleanPool",
- &error,
- &reply,
- "s", arg_all ? "all" : "hidden");
+ "CleanPool");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "s", arg_all ? "all" : "hidden");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ /* This is a slow operation, hence permit a longer time for completion. */
+ r = sd_bus_call(bus, m, USEC_INFINITY, &error, &reply);
if (r < 0)
return log_error_errno(r, "Could not clean pool: %s", bus_error_message(&error, r));
@@ -2540,35 +2591,65 @@ static int parse_argv(int argc, char *argv[]) {
};
bool reorder = false;
- int c, r;
+ int c, r, shell = -1;
assert(argc >= 0);
assert(argv);
for (;;) {
- const char * const option_string = "+hp:als:H:M:qn:o:";
+ static const char option_string[] = "-hp:als:H:M:qn:o:";
c = getopt_long(argc, argv, option_string + reorder, options, NULL);
- if (c < 0) {
+ if (c < 0)
+ break;
+
+ switch (c) {
+
+ case 1: /* getopt_long() returns 1 if "-" was the first character of the option string, and a
+ * non-option argument was discovered. */
+
+ assert(!reorder);
+
/* We generally are fine with the fact that getopt_long() reorders the command line, and looks
* for switches after the main verb. However, for "shell" we really don't want that, since we
- * want that switches passed after that are passed to the program to execute, and not processed
- * by us. To make this possible, we'll first invoke getopt_long() with reordering disabled
- * (i.e. with the "+" prefix in the option string), and as soon as we hit the end (i.e. the
- * verb) we check if that's "shell". If it is, we exit the loop, since we don't want any
- * further options processed. However, if it is anything else, we process the same argument
- * again, but this time allow reordering. */
-
- if (!reorder && optind < argc && !streq(argv[optind], "shell")) {
+ * want that switches specified after the machine name are passed to the program to execute,
+ * and not processed by us. To make this possible, we'll first invoke getopt_long() with
+ * reordering disabled (i.e. with the "-" prefix in the option string), looking for the first
+ * non-option parameter. If it's the verb "shell" we remember its position and continue
+ * processing options. In this case, as soon as we hit the next non-option argument we found
+ * the machine name, and stop further processing. If the first non-option argument is any other
+ * verb than "shell" we switch to normal reordering mode and continue processing arguments
+ * normally. */
+
+ if (shell >= 0) {
+ /* If we already found the "shell" verb on the command line, and now found the next
+ * non-option argument, then this is the machine name and we should stop processing
+ * further arguments. */
+ optind --; /* don't process this argument, go one step back */
+ goto done;
+ }
+ if (streq(optarg, "shell"))
+ /* Remember the position of the "shell" verb, and continue processing normally. */
+ shell = optind - 1;
+ else {
+ int saved_optind;
+
+ /* OK, this is some other verb. In this case, turn on reordering again, and continue
+ * processing normally. */
reorder = true;
- optind--;
- continue;
+
+ /* We changed the option string. getopt_long() only looks at it again if we invoke it
+ * at least once with a reset option index. Hence, let's reset the option index here,
+ * then invoke getopt_long() again (ignoring what it has to say, after all we most
+ * likely already processed it), and the bump the option index so that we read the
+ * intended argument again. */
+ saved_optind = optind;
+ optind = 0;
+ (void) getopt_long(argc, argv, option_string + reorder, options, NULL);
+ optind = saved_optind - 1; /* go one step back, process this argument again */
}
break;
- }
-
- switch (c) {
case 'h':
return help(0, NULL, NULL);
@@ -2704,6 +2785,22 @@ static int parse_argv(int argc, char *argv[]) {
}
}
+done:
+ if (shell >= 0) {
+ char *t;
+ int i;
+
+ /* We found the "shell" verb while processing the argument list. Since we turned off reordering of the
+ * argument list initially let's readjust it now, and move the "shell" verb to the back. */
+
+ optind -= 1; /* place the option index where the "shell" verb will be placed */
+
+ t = argv[shell];
+ for (i = shell; i < optind; i++)
+ argv[i] = argv[i+1];
+ argv[optind] = t;
+ }
+
return 1;
}
@@ -2720,6 +2817,7 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
{ "terminate", 2, VERB_ANY, 0, terminate_machine },
{ "reboot", 2, VERB_ANY, 0, reboot_machine },
{ "poweroff", 2, VERB_ANY, 0, poweroff_machine },
+ { "stop", 2, VERB_ANY, 0, poweroff_machine }, /* Convenience alias */
{ "kill", 2, VERB_ANY, 0, kill_machine },
{ "login", VERB_ANY, 2, 0, login_machine },
{ "shell", VERB_ANY, VERB_ANY, 0, shell_machine },
@@ -2750,7 +2848,7 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
}
int main(int argc, char*argv[]) {
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ sd_bus *bus = NULL;
int r;
setlocale(LC_ALL, "");
@@ -2772,6 +2870,7 @@ int main(int argc, char*argv[]) {
r = machinectl_main(argc, argv, bus);
finish:
+ sd_bus_flush_close_unref(bus);
pager_close();
polkit_agent_close();
diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c
index 31efa3695b..1923e8b971 100644
--- a/src/machine/machined-dbus.c
+++ b/src/machine/machined-dbus.c
@@ -29,6 +29,7 @@
#include "bus-util.h"
#include "cgroup-util.h"
#include "fd-util.h"
+#include "fileio.h"
#include "formats-util.h"
#include "hostname-util.h"
#include "image-dbus.h"
@@ -822,22 +823,106 @@ static int method_mark_image_read_only(sd_bus_message *message, void *userdata,
return bus_image_method_mark_read_only(message, i, error);
}
+static int clean_pool_done(Operation *operation, int ret, sd_bus_error *error) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ bool success;
+ size_t n;
+ int r;
+
+ assert(operation);
+ assert(operation->extra_fd >= 0);
+
+ if (lseek(operation->extra_fd, 0, SEEK_SET) == (off_t) -1)
+ return -errno;
+
+ f = fdopen(operation->extra_fd, "re");
+ if (!f)
+ return -errno;
+
+ operation->extra_fd = -1;
+
+ /* The resulting temporary file starts with a boolean value that indicates success or not. */
+ errno = 0;
+ n = fread(&success, 1, sizeof(success), f);
+ if (n != sizeof(success))
+ return ret < 0 ? ret : (errno != 0 ? -errno : -EIO);
+
+ if (ret < 0) {
+ _cleanup_free_ char *name = NULL;
+
+ /* The clean-up operation failed. In this case the resulting temporary file should contain a boolean
+ * set to false followed by the name of the failed image. Let's try to read this and use it for the
+ * error message. If we can't read it, don't mind, and return the naked error. */
+
+ if (success) /* The resulting temporary file could not be updated, ignore it. */
+ return ret;
+
+ r = read_nul_string(f, &name);
+ if (r < 0 || isempty(name)) /* Same here... */
+ return ret;
+
+ return sd_bus_error_set_errnof(error, ret, "Failed to remove image %s: %m", name);
+ }
+
+ assert(success);
+
+ r = sd_bus_message_new_method_return(operation->message, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "(st)");
+ if (r < 0)
+ return r;
+
+ /* On success the resulting temporary file will contain a list of image names that were removed followed by
+ * their size on disk. Let's read that and turn it into a bus message. */
+ for (;;) {
+ _cleanup_free_ char *name = NULL;
+ uint64_t size;
+
+ r = read_nul_string(f, &name);
+ if (r < 0)
+ return r;
+ if (isempty(name)) /* reached the end */
+ break;
+
+ errno = 0;
+ n = fread(&size, 1, sizeof(size), f);
+ if (n != sizeof(size))
+ return errno != 0 ? -errno : -EIO;
+
+ r = sd_bus_message_append(reply, "(st)", name, size);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(reply);
+ if (r < 0)
+ return r;
+
+ return sd_bus_send(NULL, reply, NULL);
+}
+
static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_error *error) {
enum {
REMOVE_ALL,
REMOVE_HIDDEN,
} mode;
- _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
- _cleanup_(image_hashmap_freep) Hashmap *images = NULL;
+ _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
+ _cleanup_close_ int result_fd = -1;
Manager *m = userdata;
- Image *image;
+ Operation *operation;
const char *mm;
- Iterator i;
+ pid_t child;
int r;
assert(message);
+ if (m->n_operations >= OPERATIONS_MAX)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
+
r = sd_bus_message_read(message, "s", &mm);
if (r < 0)
return r;
@@ -863,50 +948,109 @@ static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_err
if (r == 0)
return 1; /* Will call us back */
- images = hashmap_new(&string_hash_ops);
- if (!images)
- return -ENOMEM;
+ if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
- r = image_discover(images);
- if (r < 0)
- return r;
+ /* Create a temporary file we can dump information about deleted images into. We use a temporary file for this
+ * instead of a pipe or so, since this might grow quit large in theory and we don't want to process this
+ * continuously */
+ result_fd = open_tmpfile_unlinkable("/tmp/", O_RDWR|O_CLOEXEC);
+ if (result_fd < 0)
+ return -errno;
- r = sd_bus_message_new_method_return(message, &reply);
- if (r < 0)
- return r;
+ /* This might be a slow operation, run it asynchronously in a background process */
+ child = fork();
+ if (child < 0)
+ return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
- r = sd_bus_message_open_container(reply, 'a', "(st)");
- if (r < 0)
- return r;
+ if (child == 0) {
+ _cleanup_(image_hashmap_freep) Hashmap *images = NULL;
+ bool success = true;
+ Image *image;
+ Iterator i;
+ ssize_t l;
- HASHMAP_FOREACH(image, images, i) {
+ errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
- /* We can't remove vendor images (i.e. those in /usr) */
- if (IMAGE_IS_VENDOR(image))
- continue;
+ images = hashmap_new(&string_hash_ops);
+ if (!images) {
+ r = -ENOMEM;
+ goto child_fail;
+ }
- if (IMAGE_IS_HOST(image))
- continue;
+ r = image_discover(images);
+ if (r < 0)
+ goto child_fail;
- if (mode == REMOVE_HIDDEN && !IMAGE_IS_HIDDEN(image))
- continue;
+ l = write(result_fd, &success, sizeof(success));
+ if (l < 0) {
+ r = -errno;
+ goto child_fail;
+ }
- r = image_remove(image);
- if (r == -EBUSY) /* keep images that are currently being used. */
- continue;
- if (r < 0)
- return sd_bus_error_set_errnof(error, r, "Failed to remove image %s: %m", image->name);
+ HASHMAP_FOREACH(image, images, i) {
- r = sd_bus_message_append(reply, "(st)", image->name, image->usage_exclusive);
- if (r < 0)
- return r;
+ /* We can't remove vendor images (i.e. those in /usr) */
+ if (IMAGE_IS_VENDOR(image))
+ continue;
+
+ if (IMAGE_IS_HOST(image))
+ continue;
+
+ if (mode == REMOVE_HIDDEN && !IMAGE_IS_HIDDEN(image))
+ continue;
+
+ r = image_remove(image);
+ if (r == -EBUSY) /* keep images that are currently being used. */
+ continue;
+ if (r < 0) {
+ /* If the operation failed, let's override everything we wrote, and instead write there at which image we failed. */
+ success = false;
+ (void) ftruncate(result_fd, 0);
+ (void) lseek(result_fd, 0, SEEK_SET);
+ (void) write(result_fd, &success, sizeof(success));
+ (void) write(result_fd, image->name, strlen(image->name)+1);
+ goto child_fail;
+ }
+
+ l = write(result_fd, image->name, strlen(image->name)+1);
+ if (l < 0) {
+ r = -errno;
+ goto child_fail;
+ }
+
+ l = write(result_fd, &image->usage_exclusive, sizeof(image->usage_exclusive));
+ if (l < 0) {
+ r = -errno;
+ goto child_fail;
+ }
+ }
+
+ result_fd = safe_close(result_fd);
+ _exit(EXIT_SUCCESS);
+
+ child_fail:
+ (void) write(errno_pipe_fd[1], &r, sizeof(r));
+ _exit(EXIT_FAILURE);
}
- r = sd_bus_message_close_container(reply);
- if (r < 0)
+ errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
+
+ /* The clean-up might take a while, hence install a watch on the child and return */
+
+ r = operation_new(m, NULL, child, message, errno_pipe_fd[0], &operation);
+ if (r < 0) {
+ (void) sigkill_wait(child);
return r;
+ }
- return sd_bus_send(NULL, reply, NULL);
+ operation->extra_fd = result_fd;
+ operation->done = clean_pool_done;
+
+ result_fd = -1;
+ errno_pipe_fd[0] = -1;
+
+ return 1;
}
static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
diff --git a/src/machine/machined.c b/src/machine/machined.c
index f7ceb5e603..57121945f3 100644
--- a/src/machine/machined.c
+++ b/src/machine/machined.c
@@ -303,7 +303,7 @@ void manager_gc(Manager *m, bool drop_not_started) {
machine_get_state(machine) != MACHINE_CLOSING)
machine_stop(machine);
- /* Now, the stop stop probably made this referenced
+ /* Now, the stop probably made this referenced
* again, but if it didn't, then it's time to let it
* go entirely. */
if (!machine_check_gc(machine, drop_not_started)) {
diff --git a/src/machine/operation.c b/src/machine/operation.c
index e6ddc41a55..2bf93cb493 100644
--- a/src/machine/operation.c
+++ b/src/machine/operation.c
@@ -30,7 +30,7 @@ static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdat
assert(o);
assert(si);
- log_debug("Operating " PID_FMT " is now complete with with code=%s status=%i",
+ log_debug("Operating " PID_FMT " is now complete with code=%s status=%i",
o->pid,
sigchld_code_to_string(si->si_code), si->si_status);
@@ -41,18 +41,33 @@ static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdat
goto fail;
}
- if (si->si_status != EXIT_SUCCESS) {
- if (read(o->errno_fd, &r, sizeof(r)) == sizeof(r))
- r = sd_bus_error_set_errnof(&error, r, "%m");
- else
- r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed.");
-
+ if (si->si_status == EXIT_SUCCESS)
+ r = 0;
+ else if (read(o->errno_fd, &r, sizeof(r)) != sizeof(r)) { /* Try to acquire error code for failed operation */
+ r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed.");
goto fail;
}
- r = sd_bus_reply_method_return(o->message, NULL);
- if (r < 0)
- log_error_errno(r, "Failed to reply to message: %m");
+ if (o->done) {
+ /* A completion routine is set for this operation, call it. */
+ r = o->done(o, r, &error);
+ if (r < 0) {
+ if (!sd_bus_error_is_set(&error))
+ sd_bus_error_set_errno(&error, r);
+
+ goto fail;
+ }
+
+ } else {
+ /* The default operation when done is to simply return an error on failure or an empty success
+ * message on success. */
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_reply_method_return(o->message, NULL);
+ if (r < 0)
+ log_error_errno(r, "Failed to reply to message: %m");
+ }
operation_free(o);
return 0;
@@ -66,7 +81,7 @@ fail:
return 0;
}
-int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd) {
+int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd, Operation **ret) {
Operation *o;
int r;
@@ -79,6 +94,8 @@ int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_messag
if (!o)
return -ENOMEM;
+ o->extra_fd = -1;
+
r = sd_event_add_child(manager->event, &o->event_source, child, WEXITED, operation_done, o);
if (r < 0) {
free(o);
@@ -102,6 +119,9 @@ int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_messag
/* At this point we took ownership of both the child and the errno file descriptor! */
+ if (ret)
+ *ret = o;
+
return 0;
}
@@ -112,6 +132,7 @@ Operation *operation_free(Operation *o) {
sd_event_source_unref(o->event_source);
safe_close(o->errno_fd);
+ safe_close(o->extra_fd);
if (o->pid > 1)
(void) sigkill_wait(o->pid);
diff --git a/src/machine/operation.h b/src/machine/operation.h
index 7ca47bc3af..9831b123d7 100644
--- a/src/machine/operation.h
+++ b/src/machine/operation.h
@@ -38,10 +38,12 @@ struct Operation {
pid_t pid;
sd_bus_message *message;
int errno_fd;
+ int extra_fd;
sd_event_source *event_source;
+ int (*done)(Operation *o, int ret, sd_bus_error *error);
LIST_FIELDS(Operation, operations);
LIST_FIELDS(Operation, operations_by_machine);
};
-int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd);
+int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd, Operation **ret);
Operation *operation_free(Operation *o);
diff --git a/src/machine/org.freedesktop.machine1.conf b/src/machine/org.freedesktop.machine1.conf
index 9d40b90151..562b9d3cc0 100644
--- a/src/machine/org.freedesktop.machine1.conf
+++ b/src/machine/org.freedesktop.machine1.conf
@@ -118,6 +118,10 @@
<allow send_destination="org.freedesktop.machine1"
send_interface="org.freedesktop.machine1.Manager"
+ send_member="CleanPool"/>
+
+ <allow send_destination="org.freedesktop.machine1"
+ send_interface="org.freedesktop.machine1.Manager"
send_member="MapFromMachineUser"/>
<allow send_destination="org.freedesktop.machine1"
diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index 367c340e08..5498e352d8 100644
--- a/src/network/networkd-address.c
+++ b/src/network/networkd-address.c
@@ -32,6 +32,9 @@
#include "utf8.h"
#include "util.h"
+#define ADDRESSES_PER_LINK_MAX 2048U
+#define STATIC_ADDRESSES_PER_NETWORK_MAX 1024U
+
int address_new(Address **ret) {
_cleanup_address_free_ Address *address = NULL;
@@ -54,6 +57,9 @@ int address_new_static(Network *network, unsigned section, Address **ret) {
_cleanup_address_free_ Address *address = NULL;
int r;
+ assert(network);
+ assert(ret);
+
if (section) {
address = hashmap_get(network->addresses_by_section, UINT_TO_PTR(section));
if (address) {
@@ -64,18 +70,21 @@ int address_new_static(Network *network, unsigned section, Address **ret) {
}
}
+ if (network->n_static_addresses >= STATIC_ADDRESSES_PER_NETWORK_MAX)
+ return -E2BIG;
+
r = address_new(&address);
if (r < 0)
return r;
if (section) {
address->section = section;
- hashmap_put(network->addresses_by_section,
- UINT_TO_PTR(address->section), address);
+ hashmap_put(network->addresses_by_section, UINT_TO_PTR(address->section), address);
}
address->network = network;
LIST_APPEND(addresses, network->static_addresses, address);
+ network->n_static_addresses++;
*ret = address;
address = NULL;
@@ -89,10 +98,11 @@ void address_free(Address *address) {
if (address->network) {
LIST_REMOVE(addresses, address->network->static_addresses, address);
+ assert(address->network->n_static_addresses > 0);
+ address->network->n_static_addresses--;
if (address->section)
- hashmap_remove(address->network->addresses_by_section,
- UINT_TO_PTR(address->section));
+ hashmap_remove(address->network->addresses_by_section, UINT_TO_PTR(address->section));
}
if (address->link) {
@@ -328,7 +338,12 @@ static int address_release(Address *address) {
return 0;
}
-int address_update(Address *address, unsigned char flags, unsigned char scope, struct ifa_cacheinfo *cinfo) {
+int address_update(
+ Address *address,
+ unsigned char flags,
+ unsigned char scope,
+ const struct ifa_cacheinfo *cinfo) {
+
bool ready;
int r;
@@ -383,31 +398,38 @@ int address_drop(Address *address) {
return 0;
}
-int address_get(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret) {
- Address address = {}, *existing;
+int address_get(Link *link,
+ int family,
+ const union in_addr_union *in_addr,
+ unsigned char prefixlen,
+ Address **ret) {
+
+ Address address, *existing;
assert(link);
assert(in_addr);
- assert(ret);
- address.family = family;
- address.in_addr = *in_addr;
- address.prefixlen = prefixlen;
+ address = (Address) {
+ .family = family,
+ .in_addr = *in_addr,
+ .prefixlen = prefixlen,
+ };
existing = set_get(link->addresses, &address);
if (existing) {
- *ret = existing;
-
+ if (ret)
+ *ret = existing;
return 1;
- } else {
- existing = set_get(link->addresses_foreign, &address);
- if (!existing)
- return -ENOENT;
}
- *ret = existing;
+ existing = set_get(link->addresses_foreign, &address);
+ if (existing) {
+ if (ret)
+ *ret = existing;
+ return 0;
+ }
- return 0;
+ return -ENOENT;
}
int address_remove(
@@ -509,7 +531,12 @@ static int address_acquire(Link *link, Address *original, Address **ret) {
return 0;
}
-int address_configure(Address *address, Link *link, sd_netlink_message_handler_t callback, bool update) {
+int address_configure(
+ Address *address,
+ Link *link,
+ sd_netlink_message_handler_t callback,
+ bool update) {
+
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
int r;
@@ -520,6 +547,11 @@ int address_configure(Address *address, Link *link, sd_netlink_message_handler_t
assert(link->manager);
assert(link->manager->rtnl);
+ /* If this is a new address, then refuse adding more than the limit */
+ if (address_get(link, address->family, &address->in_addr, address->prefixlen, NULL) <= 0 &&
+ set_size(link->addresses) >= ADDRESSES_PER_LINK_MAX)
+ return -E2BIG;
+
r = address_acquire(link, address, &address);
if (r < 0)
return r;
diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h
index 784ab18b27..03c4bea7c6 100644
--- a/src/network/networkd-address.h
+++ b/src/network/networkd-address.h
@@ -63,7 +63,7 @@ void address_free(Address *address);
int address_add_foreign(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
int address_add(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
int address_get(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
-int address_update(Address *address, unsigned char flags, unsigned char scope, struct ifa_cacheinfo *cinfo);
+int address_update(Address *address, unsigned char flags, unsigned char scope, const struct ifa_cacheinfo *cinfo);
int address_drop(Address *address);
int address_configure(Address *address, Link *link, sd_netlink_message_handler_t callback, bool update);
int address_remove(Address *address, Link *link, sd_netlink_message_handler_t callback);
diff --git a/src/network/networkd-brvlan.c b/src/network/networkd-brvlan.c
new file mode 100644
index 0000000000..8bc330ebae
--- /dev/null
+++ b/src/network/networkd-brvlan.c
@@ -0,0 +1,329 @@
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2016 BISDN GmbH. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <stdbool.h>
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "netlink-util.h"
+#include "networkd-brvlan.h"
+#include "networkd.h"
+#include "parse-util.h"
+#include "vlan-util.h"
+
+static bool is_bit_set(unsigned bit, uint32_t scope) {
+ assert(bit < sizeof(scope)*8);
+ return scope & (1 << bit);
+}
+
+static inline void set_bit(unsigned nr, uint32_t *addr) {
+ if (nr < BRIDGE_VLAN_BITMAP_MAX)
+ addr[nr / 32] |= (((uint32_t) 1) << (nr % 32));
+}
+
+static int find_next_bit(int i, uint32_t x) {
+ int j;
+
+ if (i >= 32)
+ return -1;
+
+ /* find first bit */
+ if (i < 0)
+ return BUILTIN_FFS_U32(x);
+
+ /* mask off prior finds to get next */
+ j = __builtin_ffs(x >> i);
+ return j ? j + i : 0;
+}
+
+static int append_vlan_info_data(Link *const link, sd_netlink_message *req, uint16_t pvid, const uint32_t *br_vid_bitmap, const uint32_t *br_untagged_bitmap) {
+ struct bridge_vlan_info br_vlan;
+ int i, j, k, r, done, cnt;
+ uint16_t begin, end;
+ bool untagged = false;
+
+ assert(link);
+ assert(req);
+ assert(br_vid_bitmap);
+ assert(br_untagged_bitmap);
+
+ i = cnt = -1;
+
+ begin = end = UINT16_MAX;
+ for (k = 0; k < BRIDGE_VLAN_BITMAP_LEN; k++) {
+ unsigned base_bit;
+ uint32_t vid_map = br_vid_bitmap[k];
+ uint32_t untagged_map = br_untagged_bitmap[k];
+
+ base_bit = k * 32;
+ i = -1;
+ done = 0;
+ do {
+ j = find_next_bit(i, vid_map);
+ if (j > 0) {
+ /* first hit of any bit */
+ if (begin == UINT16_MAX && end == UINT16_MAX) {
+ begin = end = j - 1 + base_bit;
+ untagged = is_bit_set(j - 1, untagged_map);
+ goto next;
+ }
+
+ /* this bit is a continuation of prior bits */
+ if (j - 2 + base_bit == end && untagged == is_bit_set(j - 1, untagged_map) && (uint16_t)j - 1 + base_bit != pvid && (uint16_t)begin != pvid) {
+ end++;
+ goto next;
+ }
+ } else
+ done = 1;
+
+ if (begin != UINT16_MAX) {
+ cnt++;
+ if (done && k < BRIDGE_VLAN_BITMAP_LEN - 1)
+ break;
+
+ br_vlan.flags = 0;
+ if (untagged)
+ br_vlan.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
+
+ if (begin == end) {
+ br_vlan.vid = begin;
+
+ if (begin == pvid)
+ br_vlan.flags |= BRIDGE_VLAN_INFO_PVID;
+
+ r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m");
+ } else {
+ br_vlan.vid = begin;
+ br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
+
+ r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m");
+
+ br_vlan.vid = end;
+ br_vlan.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN;
+ br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_END;
+
+ r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m");
+ }
+
+ if (done)
+ break;
+ }
+ if (j > 0) {
+ begin = end = j - 1 + base_bit;
+ untagged = is_bit_set(j - 1, untagged_map);
+ }
+
+ next:
+ i = j;
+ } while(!done);
+ }
+ if (!cnt)
+ return -EINVAL;
+
+ return cnt;
+}
+
+static int set_brvlan_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
+ Link *link = userdata;
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_error_errno(link, r, "Could not add VLAN to bridge port: %m");
+
+ return 1;
+}
+
+int br_vlan_configure(Link *link, uint16_t pvid, uint32_t *br_vid_bitmap, uint32_t *br_untagged_bitmap) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+ uint16_t flags;
+ sd_netlink *rtnl;
+
+ assert(link);
+ assert(link->manager);
+ assert(br_vid_bitmap);
+ assert(br_untagged_bitmap);
+ assert(link->network);
+
+ /* pvid might not be in br_vid_bitmap yet */
+ if (pvid)
+ set_bit(pvid, br_vid_bitmap);
+
+ rtnl = link->manager->rtnl;
+
+ /* create new RTM message */
+ r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+
+ r = sd_rtnl_message_link_set_family(req, PF_BRIDGE);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set message family: %m");
+
+ r = sd_netlink_message_open_container(req, IFLA_AF_SPEC);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not open IFLA_AF_SPEC container: %m");
+
+ /* master needs flag self */
+ if (!link->network->bridge) {
+ flags = BRIDGE_FLAGS_SELF;
+ sd_netlink_message_append_data(req, IFLA_BRIDGE_FLAGS, &flags, sizeof(uint16_t));
+ }
+
+ /* add vlan info */
+ r = append_vlan_info_data(link, req, pvid, br_vid_bitmap, br_untagged_bitmap);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append VLANs: %m");
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not close IFLA_AF_SPEC container: %m");
+
+ /* send message to the kernel */
+ r = sd_netlink_call_async(rtnl, req, set_brvlan_handler, link, 0, NULL);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ return 0;
+}
+
+static int parse_vid_range(const char *rvalue, uint16_t *vid, uint16_t *vid_end) {
+ int r;
+ char *p;
+ char *_rvalue = NULL;
+ uint16_t _vid = UINT16_MAX;
+ uint16_t _vid_end = UINT16_MAX;
+
+ assert(rvalue);
+ assert(vid);
+ assert(vid_end);
+
+ _rvalue = strdupa(rvalue);
+ p = strchr(_rvalue, '-');
+ if (p) {
+ *p = '\0';
+ p++;
+ r = parse_vlanid(_rvalue, &_vid);
+ if (r < 0)
+ return r;
+
+ if (_vid == 0)
+ return -ERANGE;
+
+ r = parse_vlanid(p, &_vid_end);
+ if (r < 0)
+ return r;
+
+ if (_vid_end == 0)
+ return -ERANGE;
+ } else {
+ r = parse_vlanid(_rvalue, &_vid);
+ if (r < 0)
+ return r;
+
+ if (_vid == 0)
+ return -ERANGE;
+ }
+
+ *vid = _vid;
+ *vid_end = _vid_end;
+ return r;
+}
+
+int config_parse_brvlan_vlan(const char *unit, const char *filename,
+ unsigned line, const char *section,
+ unsigned section_line, const char *lvalue,
+ int ltype, const char *rvalue, void *data,
+ void *userdata) {
+ Network *network = userdata;
+ int r;
+ uint16_t vid, vid_end;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = parse_vid_range(rvalue, &vid, &vid_end);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse VLAN, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (UINT16_MAX == vid_end)
+ set_bit(vid++, network->br_vid_bitmap);
+ else {
+ if (vid >= vid_end) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid VLAN range, ignoring %s", rvalue);
+ return 0;
+ }
+ for (; vid <= vid_end; vid++)
+ set_bit(vid, network->br_vid_bitmap);
+ }
+ return 0;
+}
+
+int config_parse_brvlan_untagged(const char *unit, const char *filename,
+ unsigned line, const char *section,
+ unsigned section_line, const char *lvalue,
+ int ltype, const char *rvalue, void *data,
+ void *userdata) {
+ Network *network = userdata;
+ int r;
+ uint16_t vid, vid_end;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = parse_vid_range(rvalue, &vid, &vid_end);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse VLAN: %s", rvalue);
+ return 0;
+ }
+
+ if (UINT16_MAX == vid_end) {
+ set_bit(vid, network->br_vid_bitmap);
+ set_bit(vid, network->br_untagged_bitmap);
+ } else {
+ if (vid >= vid_end) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid VLAN range, ignoring %s", rvalue);
+ return 0;
+ }
+ for (; vid <= vid_end; vid++) {
+ set_bit(vid, network->br_vid_bitmap);
+ set_bit(vid, network->br_untagged_bitmap);
+ }
+ }
+ return 0;
+}
diff --git a/src/network/networkd-brvlan.h b/src/network/networkd-brvlan.h
new file mode 100644
index 0000000000..6aa6883bfc
--- /dev/null
+++ b/src/network/networkd-brvlan.h
@@ -0,0 +1,29 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2016 BISDN GmbH. All rights reserved.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdint.h>
+
+typedef struct Link Link;
+
+int br_vlan_configure(Link *link, uint16_t pvid, uint32_t *br_vid_bitmap, uint32_t *br_untagged_bitmap);
+
+int config_parse_brvlan_vlan(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_brvlan_untagged(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c
index b67a1f6d09..c03e2b2ebf 100644
--- a/src/network/networkd-conf.c
+++ b/src/network/networkd-conf.c
@@ -70,7 +70,7 @@ int config_parse_duid_rawdata(
for (;;) {
int n1, n2, len, r;
uint32_t byte;
- _cleanup_free_ char *cbyte = NULL;
+ _cleanup_free_ char *cbyte = NULL;
r = extract_first_word(&rvalue, &cbyte, ":", 0);
if (r < 0) {
diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c
index 50721b1c74..15acf56a5f 100644
--- a/src/network/networkd-dhcp6.c
+++ b/src/network/networkd-dhcp6.c
@@ -60,10 +60,15 @@ static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m,
return 1;
}
-static int dhcp6_address_change(Link *link, struct in6_addr *ip6_addr,
- uint32_t lifetime_preferred, uint32_t lifetime_valid) {
- int r;
+static int dhcp6_address_change(
+ Link *link,
+ struct in6_addr *ip6_addr,
+ uint32_t lifetime_preferred,
+ uint32_t lifetime_valid) {
+
_cleanup_address_free_ Address *addr = NULL;
+ char buffer[INET6_ADDRSTRLEN];
+ int r;
r = address_new(&addr);
if (r < 0)
@@ -79,8 +84,8 @@ static int dhcp6_address_change(Link *link, struct in6_addr *ip6_addr,
addr->cinfo.ifa_valid = lifetime_valid;
log_link_info(link,
- "DHCPv6 address "SD_NDISC_ADDRESS_FORMAT_STR"/%d timeout preferred %d valid %d",
- SD_NDISC_ADDRESS_FORMAT_VAL(addr->in_addr.in6),
+ "DHCPv6 address %s/%d timeout preferred %d valid %d",
+ inet_ntop(AF_INET6, &addr->in_addr.in6, buffer, sizeof(buffer)),
addr->prefixlen, lifetime_preferred, lifetime_valid);
r = address_configure(addr, link, dhcp6_address_handler, true);
diff --git a/src/network/networkd-fdb.c b/src/network/networkd-fdb.c
index 241f486211..be8aebee2d 100644
--- a/src/network/networkd-fdb.c
+++ b/src/network/networkd-fdb.c
@@ -26,15 +26,21 @@
#include "networkd-fdb.h"
#include "networkd.h"
#include "util.h"
+#include "vlan-util.h"
+
+#define STATIC_FDB_ENTRIES_PER_NETWORK_MAX 1024U
/* create a new FDB entry or get an existing one. */
-int fdb_entry_new_static(Network *const network,
- const unsigned section,
- FdbEntry **ret) {
+int fdb_entry_new_static(
+ Network *network,
+ unsigned section,
+ FdbEntry **ret) {
+
_cleanup_fdbentry_free_ FdbEntry *fdb_entry = NULL;
struct ether_addr *mac_addr = NULL;
assert(network);
+ assert(ret);
/* search entry in hashmap first. */
if (section) {
@@ -47,6 +53,9 @@ int fdb_entry_new_static(Network *const network,
}
}
+ if (network->n_static_fdb_entries >= STATIC_FDB_ENTRIES_PER_NETWORK_MAX)
+ return -E2BIG;
+
/* allocate space for MAC address. */
mac_addr = new0(struct ether_addr, 1);
if (!mac_addr)
@@ -54,7 +63,6 @@ int fdb_entry_new_static(Network *const network,
/* allocate space for and FDB entry. */
fdb_entry = new0(FdbEntry, 1);
-
if (!fdb_entry) {
/* free previously allocated space for mac_addr. */
free(mac_addr);
@@ -66,6 +74,7 @@ int fdb_entry_new_static(Network *const network,
fdb_entry->mac_addr = mac_addr;
LIST_PREPEND(static_fdb_entries, network->static_fdb_entries, fdb_entry);
+ network->n_static_fdb_entries++;
if (section) {
fdb_entry->section = section;
@@ -94,7 +103,7 @@ static int set_fdb_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userda
}
/* send a request to the kernel to add a FDB entry in its static MAC table. */
-int fdb_entry_configure(Link *const link, FdbEntry *const fdb_entry) {
+int fdb_entry_configure(Link *link, FdbEntry *fdb_entry) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
sd_netlink *rtnl;
int r;
@@ -145,12 +154,13 @@ void fdb_entry_free(FdbEntry *fdb_entry) {
return;
if (fdb_entry->network) {
- LIST_REMOVE(static_fdb_entries, fdb_entry->network->static_fdb_entries,
- fdb_entry);
+ LIST_REMOVE(static_fdb_entries, fdb_entry->network->static_fdb_entries, fdb_entry);
+
+ assert(fdb_entry->network->n_static_fdb_entries > 0);
+ fdb_entry->network->n_static_fdb_entries--;
if (fdb_entry->section)
- hashmap_remove(fdb_entry->network->fdb_entries_by_section,
- UINT_TO_PTR(fdb_entry->section));
+ hashmap_remove(fdb_entry->network->fdb_entries_by_section, UINT_TO_PTR(fdb_entry->section));
}
free(fdb_entry->mac_addr);
@@ -231,9 +241,9 @@ int config_parse_fdb_vlan_id(
if (r < 0)
return log_oom();
- r = config_parse_unsigned(unit, filename, line, section,
- section_line, lvalue, ltype,
- rvalue, &fdb_entry->vlan_id, userdata);
+ r = config_parse_vlanid(unit, filename, line, section,
+ section_line, lvalue, ltype,
+ rvalue, &fdb_entry->vlan_id, userdata);
if (r < 0)
return r;
diff --git a/src/network/networkd-fdb.h b/src/network/networkd-fdb.h
index 84410714f5..2d7d28735c 100644
--- a/src/network/networkd-fdb.h
+++ b/src/network/networkd-fdb.h
@@ -36,9 +36,9 @@ struct FdbEntry {
LIST_FIELDS(FdbEntry, static_fdb_entries);
};
-int fdb_entry_new_static(Network *const network, const unsigned section, FdbEntry **ret);
+int fdb_entry_new_static(Network *network, unsigned section, FdbEntry **ret);
void fdb_entry_free(FdbEntry *fdb_entry);
-int fdb_entry_configure(Link *const link, FdbEntry *const fdb_entry);
+int fdb_entry_configure(Link *link, FdbEntry *fdb_entry);
DEFINE_TRIVIAL_CLEANUP_FUNC(FdbEntry*, fdb_entry_free);
#define _cleanup_fdbentry_free_ _cleanup_(fdb_entry_freep)
diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c
index 735c231a4c..2d81311e81 100644
--- a/src/network/networkd-ipv4ll.c
+++ b/src/network/networkd-ipv4ll.c
@@ -138,7 +138,7 @@ static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) {
ll_addr->family = AF_INET;
ll_addr->in_addr.in = address;
ll_addr->prefixlen = 16;
- ll_addr->broadcast.s_addr = ll_addr->in_addr.in.s_addr | htonl(0xfffffffflu >> ll_addr->prefixlen);
+ ll_addr->broadcast.s_addr = ll_addr->in_addr.in.s_addr | htobe32(0xfffffffflu >> ll_addr->prefixlen);
ll_addr->scope = RT_SCOPE_LINK;
r = address_configure(ll_addr, link, ipv4ll_address_handler, false);
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index ee52b1ce1e..3e10ab1e04 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -28,8 +28,9 @@
#include "fileio.h"
#include "netlink-util.h"
#include "network-internal.h"
-#include "networkd.h"
#include "networkd-lldp-tx.h"
+#include "networkd-ndisc.h"
+#include "networkd.h"
#include "set.h"
#include "socket-util.h"
#include "stdio-util.h"
@@ -255,12 +256,8 @@ static int link_enable_ipv6(Link *link) {
r = write_string_file(p, one_zero(disabled), WRITE_STRING_FILE_VERIFY_ON_FAILURE);
if (r < 0)
log_link_warning_errno(link, r, "Cannot %s IPv6 for interface %s: %m", disabled ? "disable" : "enable", link->ifname);
- else {
- if (disabled)
- log_link_info(link, "IPv6 disabled for interface: %m");
- else
- log_link_info(link, "IPv6 enabled for interface: %m");
- }
+ else
+ log_link_info(link, "IPv6 %sd for interface: %m", enable_disable(!disabled));
return 0;
}
@@ -395,7 +392,7 @@ static int link_update_flags(Link *link, sd_netlink_message *m) {
static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) {
_cleanup_link_unref_ Link *link = NULL;
uint16_t type;
- const char *ifname;
+ const char *ifname, *kind = NULL;
int r, ifindex;
unsigned short iftype;
@@ -403,6 +400,15 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) {
assert(message);
assert(ret);
+ /* check for link kind */
+ r = sd_netlink_message_enter_container(message, IFLA_LINKINFO);
+ if (r == 0) {
+ (void)sd_netlink_message_read_string(message, IFLA_INFO_KIND, &kind);
+ r = sd_netlink_message_exit_container(message);
+ if (r < 0)
+ return r;
+ }
+
r = sd_netlink_message_get_type(message, &type);
if (r < 0)
return r;
@@ -437,6 +443,12 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) {
if (!link->ifname)
return -ENOMEM;
+ if (kind) {
+ link->kind = strdup(kind);
+ if (!link->kind)
+ return -ENOMEM;
+ }
+
r = sd_netlink_message_read_ether_addr(message, IFLA_ADDRESS, &link->mac);
if (r < 0)
log_link_debug_errno(link, r, "MAC address not found for new device, continuing without");
@@ -504,13 +516,18 @@ static void link_free(Link *link) {
sd_ipv4ll_unref(link->ipv4ll);
sd_dhcp6_client_unref(link->dhcp6_client);
- sd_ndisc_unref(link->ndisc_router_discovery);
+ sd_ndisc_unref(link->ndisc);
+
+ set_free_free(link->ndisc_rdnss);
+ set_free_free(link->ndisc_dnssl);
if (link->manager)
hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex));
free(link->ifname);
+ free(link->kind);
+
(void)unlink(link->state_file);
free(link->state_file);
@@ -616,8 +633,8 @@ static int link_stop_clients(Link *link) {
r = log_link_warning_errno(link, k, "Could not stop DHCPv6 client: %m");
}
- if (link->ndisc_router_discovery) {
- k = sd_ndisc_stop(link->ndisc_router_discovery);
+ if (link->ndisc) {
+ k = sd_ndisc_stop(link->ndisc);
if (k < 0)
r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m");
}
@@ -1092,7 +1109,17 @@ int link_address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, void *u
return 1;
}
-static int link_set_bridge_fdb(Link *const link) {
+static int link_set_bridge_vlan(Link *link) {
+ int r = 0;
+
+ r = br_vlan_configure(link, link->network->pvid, link->network->br_vid_bitmap, link->network->br_untagged_bitmap);
+ if (r < 0)
+ log_link_error_errno(link, r, "Failed to assign VLANs to bridge port: %m");
+
+ return r;
+}
+
+static int link_set_bridge_fdb(Link *link) {
FdbEntry *fdb_entry;
int r = 0;
@@ -1107,7 +1134,7 @@ static int link_set_bridge_fdb(Link *const link) {
return r;
}
-static int link_set_proxy_arp(Link *const link) {
+static int link_set_proxy_arp(Link *link) {
const char *p = NULL;
int r;
@@ -1287,6 +1314,65 @@ int link_set_mtu(Link *link, uint32_t mtu) {
return 0;
}
+static int set_flags_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
+ _cleanup_link_unref_ Link *link = userdata;
+ int r;
+
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not set link flags: %m");
+
+ return 1;
+}
+
+static int link_set_flags(Link *link) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ unsigned ifi_change = 0;
+ unsigned ifi_flags = 0;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ if (link->flags & IFF_LOOPBACK)
+ return 0;
+
+ if (!link->network)
+ return 0;
+
+ if (link->network->arp < 0)
+ return 0;
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+
+ if (link->network->arp >= 0) {
+ ifi_change |= IFF_NOARP;
+ ifi_flags |= IFF_NOARP;
+ }
+
+ r = sd_rtnl_message_link_set_flags(req, ifi_flags, ifi_change);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set link flags: %m");
+
+ r = sd_netlink_call_async(link->manager->rtnl, req, set_flags_handler, link, 0, NULL);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
static int link_set_bridge(Link *link) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
int r;
@@ -1453,11 +1539,11 @@ static int link_acquire_ipv6_conf(Link *link) {
}
if (link_ipv6_accept_ra_enabled(link)) {
- assert(link->ndisc_router_discovery);
+ assert(link->ndisc);
log_link_debug(link, "Discovering IPv6 routers");
- r = sd_ndisc_router_discovery_start(link->ndisc_router_discovery);
+ r = sd_ndisc_start(link->ndisc);
if (r < 0 && r != -EBUSY)
return log_link_warning_errno(link, r, "Could not start IPv6 Router Discovery: %m");
}
@@ -1569,7 +1655,7 @@ static int link_up(Link *link) {
return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
/* set it free if not enslaved with networkd */
- if (!link->network->bridge && !link->network->bond) {
+ if (!link->network->bridge && !link->network->bond && !link->network->vrf) {
r = sd_netlink_message_append_u32(req, IFLA_MASTER, 0);
if (r < 0)
return log_link_error_errno(link, r, "Could not append IFLA_MASTER attribute: %m");
@@ -1974,6 +2060,12 @@ static int link_joined(Link *link) {
log_link_error_errno(link, r, "Could not set bridge message: %m");
}
+ if (link->network->bridge || streq_ptr("bridge", link->kind)) {
+ r = link_set_bridge_vlan(link);
+ if (r < 0)
+ log_link_error_errno(link, r, "Could not set bridge vlan: %m");
+ }
+
return link_enter_set_addresses(link);
}
@@ -2018,6 +2110,7 @@ static int link_enter_join_netdev(Link *link) {
if (!link->network->bridge &&
!link->network->bond &&
+ !link->network->vrf &&
hashmap_isempty(link->network->stacked_netdevs))
return link_joined(link);
@@ -2064,6 +2157,26 @@ static int link_enter_join_netdev(Link *link) {
link->enslaving++;
}
+ if (link->network->vrf) {
+ log_struct(LOG_DEBUG,
+ LOG_LINK_INTERFACE(link),
+ LOG_NETDEV_INTERFACE(link->network->vrf),
+ LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->vrf->ifname),
+ NULL);
+ r = netdev_join(link->network->vrf, link, netdev_join_handler);
+ if (r < 0) {
+ log_struct_errno(LOG_WARNING, r,
+ LOG_LINK_INTERFACE(link),
+ LOG_NETDEV_INTERFACE(link->network->vrf),
+ LOG_LINK_MESSAGE(link, "Could not join netdev '%s': %m", link->network->vrf->ifname),
+ NULL);
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->enslaving++;
+ }
+
HASHMAP_FOREACH(netdev, link->network->stacked_netdevs, i) {
log_struct(LOG_DEBUG,
@@ -2115,7 +2228,7 @@ static int link_set_ipv6_forward(Link *link) {
if (!link_ipv6_forward_enabled(link))
return 0;
- /* On Linux, the IPv6 stack does not not know a per-interface
+ /* On Linux, the IPv6 stack does not know a per-interface
* packet forwarding setting: either packet forwarding is on
* for all, or off for all. We hence don't bother with a
* per-interface setting, but simply propagate the interface
@@ -2260,6 +2373,35 @@ static int link_drop_foreign_config(Link *link) {
return 0;
}
+static int link_drop_config(Link *link) {
+ Address *address;
+ Route *route;
+ Iterator i;
+ int r;
+
+ SET_FOREACH(address, link->addresses, i) {
+ /* we consider IPv6LL addresses to be managed by the kernel */
+ if (address->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &address->in_addr) == 1)
+ continue;
+
+ r = address_remove(address, link, link_address_remove_handler);
+ if (r < 0)
+ return r;
+ }
+
+ SET_FOREACH(route, link->routes, i) {
+ /* do not touch routes managed by the kernel */
+ if (route->protocol == RTPROT_KERNEL)
+ continue;
+
+ r = route_remove(route, link, link_route_remove_handler);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
static int link_update_lldp(Link *link) {
int r;
@@ -2328,6 +2470,10 @@ static int link_configure(Link *link) {
if (r < 0)
return r;
+ r = link_set_flags(link);
+ if (r < 0)
+ return r;
+
if (link_ipv4ll_enabled(link)) {
r = ipv4ll_configure(link);
if (r < 0)
@@ -2364,7 +2510,11 @@ static int link_configure(Link *link) {
}
if (link_lldp_rx_enabled(link)) {
- r = sd_lldp_new(&link->lldp, link->ifindex);
+ r = sd_lldp_new(&link->lldp);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_set_ifindex(link->lldp, link->ifindex);
if (r < 0)
return r;
@@ -2664,7 +2814,7 @@ network_file_fail:
r = sd_dhcp_client_set_request_address(link->dhcp_client, &address.in);
if (r < 0)
- return log_link_error_errno(link, r, "Falied to set inital DHCPv4 address %s: %m", dhcp4_address);
+ return log_link_error_errno(link, r, "Falied to set initial DHCPv4 address %s: %m", dhcp4_address);
}
dhcp4_address_fail:
@@ -2682,7 +2832,7 @@ dhcp4_address_fail:
r = sd_ipv4ll_set_address(link->ipv4ll, &address.in);
if (r < 0)
- return log_link_error_errno(link, r, "Falied to set inital IPv4LL address %s: %m", ipv4ll_address);
+ return log_link_error_errno(link, r, "Falied to set initial IPv4LL address %s: %m", ipv4ll_address);
}
ipv4ll_address_fail:
@@ -2802,6 +2952,14 @@ static int link_carrier_lost(Link *link) {
return r;
}
+ r = link_drop_config(link);
+ if (r < 0)
+ return r;
+
+ r = link_drop_foreign_config(link);
+ if (r < 0)
+ return r;
+
r = link_handle_bound_by_list(link);
if (r < 0)
return r;
@@ -3083,6 +3241,22 @@ int link_save(Link *link) {
if (space)
fputc(' ', f);
serialize_in6_addrs(f, in6_addrs, r);
+ space = true;
+ }
+ }
+
+ /* Make sure to flush out old entries before we use the NDISC data */
+ ndisc_vacuum(link);
+
+ if (link->network->dhcp_use_dns && link->ndisc_rdnss) {
+ NDiscRDNSS *dd;
+
+ SET_FOREACH(dd, link->ndisc_rdnss, i) {
+ if (space)
+ fputc(' ', f);
+
+ serialize_in6_addrs(f, &dd->address, 1);
+ space = true;
}
}
@@ -3128,7 +3302,6 @@ int link_save(Link *link) {
if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
if (link->dhcp_lease)
(void) sd_dhcp_lease_get_domainname(link->dhcp_lease, &dhcp_domainname);
-
if (dhcp6_lease)
(void) sd_dhcp6_lease_get_domains(dhcp6_lease, &dhcp6_domains);
}
@@ -3136,22 +3309,34 @@ int link_save(Link *link) {
fputs("DOMAINS=", f);
fputstrv(f, link->network->search_domains, NULL, &space);
- if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES && dhcp_domainname)
- fputs_with_space(f, dhcp_domainname, NULL, &space);
+ if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) {
+ NDiscDNSSL *dd;
- if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES && dhcp6_domains)
- fputstrv(f, dhcp6_domains, NULL, &space);
+ if (dhcp_domainname)
+ fputs_with_space(f, dhcp_domainname, NULL, &space);
+ if (dhcp6_domains)
+ fputstrv(f, dhcp6_domains, NULL, &space);
+
+ SET_FOREACH(dd, link->ndisc_dnssl, i)
+ fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space);
+ }
fputc('\n', f);
fputs("ROUTE_DOMAINS=", f);
fputstrv(f, link->network->route_domains, NULL, NULL);
- if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE && dhcp_domainname)
- fputs_with_space(f, dhcp_domainname, NULL, &space);
+ if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE) {
+ NDiscDNSSL *dd;
- if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE && dhcp6_domains)
- fputstrv(f, dhcp6_domains, NULL, &space);
+ if (dhcp_domainname)
+ fputs_with_space(f, dhcp_domainname, NULL, &space);
+ if (dhcp6_domains)
+ fputstrv(f, dhcp6_domains, NULL, &space);
+
+ SET_FOREACH(dd, link->ndisc_dnssl, i)
+ fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space);
+ }
fputc('\n', f);
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
index 5efefd27d6..2809b1fe0b 100644
--- a/src/network/networkd-link.h
+++ b/src/network/networkd-link.h
@@ -68,6 +68,7 @@ typedef struct Link {
int ifindex;
char *ifname;
+ char *kind;
unsigned short iftype;
char *state_file;
struct ether_addr mac;
@@ -98,6 +99,7 @@ typedef struct Link {
unsigned dhcp4_messages;
bool dhcp4_configured;
bool dhcp6_configured;
+
unsigned ndisc_messages;
bool ndisc_configured;
@@ -111,7 +113,10 @@ typedef struct Link {
sd_dhcp_server *dhcp_server;
- sd_ndisc *ndisc_router_discovery;
+ sd_ndisc *ndisc;
+ Set *ndisc_rdnss;
+ Set *ndisc_dnssl;
+
sd_dhcp6_client *dhcp6_client;
bool rtnl_extended_attrs;
@@ -161,7 +166,6 @@ int ipv4ll_configure(Link *link);
int dhcp4_configure(Link *link);
int dhcp6_configure(Link *link);
int dhcp6_request_address(Link *link, int ir);
-int ndisc_configure(Link *link);
const char* link_state_to_string(LinkState s) _const_;
LinkState link_state_from_string(const char *s) _pure_;
diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c
index 7359cc2b67..d9c18b32a5 100644
--- a/src/network/networkd-ndisc.c
+++ b/src/network/networkd-ndisc.c
@@ -17,14 +17,15 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
-#include <netinet/ether.h>
#include <netinet/icmp6.h>
-#include <netinet/in.h>
-#include <linux/if.h>
#include "sd-ndisc.h"
#include "networkd.h"
+#include "networkd-ndisc.h"
+
+#define NDISC_DNSSL_MAX 64U
+#define NDISC_RDNSS_MAX 64U
static int ndisc_netlink_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
_cleanup_link_unref_ Link *link = userdata;
@@ -49,19 +50,92 @@ static int ndisc_netlink_handler(sd_netlink *rtnl, sd_netlink_message *m, void *
return 1;
}
-static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen,
- unsigned lifetime_preferred, unsigned lifetime_valid, void *userdata) {
- _cleanup_address_free_ Address *address = NULL;
- Link *link = userdata;
+static void ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
+ _cleanup_route_free_ Route *route = NULL;
+ struct in6_addr gateway;
+ uint16_t lifetime;
+ unsigned preference;
usec_t time_now;
int r;
- assert(nd);
assert(link);
- assert(link->network);
+ assert(rt);
- if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ r = sd_ndisc_router_get_lifetime(rt, &lifetime);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
+ return;
+ }
+ if (lifetime == 0) /* not a default router */
+ return;
+
+ r = sd_ndisc_router_get_address(rt, &gateway);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
+ return;
+ }
+
+ r = sd_ndisc_router_get_preference(rt, &preference);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
+ return;
+ }
+
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+ return;
+ }
+
+ r = route_new(&route);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Could not allocate route: %m");
+ return;
+ }
+
+ route->family = AF_INET6;
+ route->table = RT_TABLE_MAIN;
+ route->protocol = RTPROT_RA;
+ route->pref = preference;
+ route->gw.in6 = gateway;
+ route->lifetime = time_now + lifetime * USEC_PER_SEC;
+
+ r = route_configure(route, link, ndisc_netlink_handler);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set default route: %m");
+ link_enter_failed(link);
+ return;
+ }
+
+ link->ndisc_messages++;
+}
+
+static void ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) {
+ _cleanup_address_free_ Address *address = NULL;
+ uint32_t lifetime_valid, lifetime_preferred;
+ unsigned prefixlen;
+ int r;
+
+ assert(link);
+ assert(rt);
+
+ r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Failed to get prefix length: %m");
+ return;
+ }
+
+ r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Failed to get prefix valid lifetime: %m");
return;
+ }
+
+ r = sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Failed to get prefix preferred lifetime: %m");
+ return;
+ }
r = address_new(&address);
if (r < 0) {
@@ -69,10 +143,13 @@ static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr
return;
}
- assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);
-
address->family = AF_INET6;
- address->in_addr.in6 = *prefix;
+ r = sd_ndisc_router_prefix_get_address(rt, &address->in_addr.in6);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Failed to get prefix address: %m");
+ return;
+ }
+
if (in_addr_is_null(AF_INET6, (const union in_addr_union *) &link->network->ipv6_token) == 0)
memcpy(((char *)&address->in_addr.in6) + 8, ((char *)&link->network->ipv6_token) + 8, 8);
else {
@@ -102,17 +179,33 @@ static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr
link->ndisc_messages++;
}
-static void ndisc_prefix_onlink_handler(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, unsigned lifetime, void *userdata) {
+static void ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
_cleanup_route_free_ Route *route = NULL;
- Link *link = userdata;
usec_t time_now;
+ uint32_t lifetime;
+ unsigned prefixlen;
int r;
- assert(nd);
assert(link);
+ assert(rt);
- if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
+ }
+
+ r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Failed to get prefix length: %m");
+ return;
+ }
+
+ r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Failed to get prefix lifetime: %m");
+ return;
+ }
r = route_new(&route);
if (r < 0) {
@@ -120,16 +213,19 @@ static void ndisc_prefix_onlink_handler(sd_ndisc *nd, const struct in6_addr *pre
return;
}
- assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);
-
route->family = AF_INET6;
route->table = RT_TABLE_MAIN;
route->protocol = RTPROT_RA;
route->flags = RTM_F_PREFIX;
- route->dst.in6 = *prefix;
route->dst_prefixlen = prefixlen;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
+ r = sd_ndisc_router_prefix_get_address(rt, &route->dst.in6);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Failed to get prefix address: %m");
+ return;
+ }
+
r = route_configure(route, link, ndisc_netlink_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set prefix route: %m");
@@ -140,32 +236,47 @@ static void ndisc_prefix_onlink_handler(sd_ndisc *nd, const struct in6_addr *pre
link->ndisc_messages++;
}
-static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata) {
+static void ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
_cleanup_route_free_ Route *route = NULL;
- Link *link = userdata;
+ struct in6_addr gateway;
+ uint32_t lifetime;
+ unsigned preference, prefixlen;
usec_t time_now;
int r;
assert(link);
- assert(link->network);
- assert(link->manager);
- assert(link->dhcp6_client);
- assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0);
- if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ r = sd_ndisc_router_route_get_lifetime(rt, &lifetime);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
+ return;
+ }
+ if (lifetime == 0)
return;
- if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)) {
- /* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
- r = dhcp6_request_address(link, flags & ND_RA_FLAG_MANAGED ? false : true);
- if (r < 0 && r != -EBUSY)
- log_link_warning_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m");
- else
- log_link_debug(link, "Acquiring DHCPv6 lease on NDisc request");
+ r = sd_ndisc_router_get_address(rt, &gateway);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
+ return;
+ }
+
+ r = sd_ndisc_router_route_get_prefixlen(rt, &prefixlen);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get route prefix length: %m");
+ return;
+ }
+
+ r = sd_ndisc_router_route_get_preference(rt, &preference);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
+ return;
}
- if (!gateway)
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
+ }
r = route_new(&route);
if (r < 0) {
@@ -173,18 +284,23 @@ static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_a
return;
}
- assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);
-
route->family = AF_INET6;
route->table = RT_TABLE_MAIN;
route->protocol = RTPROT_RA;
- route->pref = pref;
- route->gw.in6 = *gateway;
+ route->pref = preference;
+ route->gw.in6 = gateway;
+ route->dst_prefixlen = prefixlen;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
+ r = sd_ndisc_router_route_get_address(rt, &route->dst.in6);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Failed to get route address: %m");
+ return;
+ }
+
r = route_configure(route, link, ndisc_netlink_handler);
if (r < 0) {
- log_link_warning_errno(link, r, "Could not set default route: %m");
+ log_link_warning_errno(link, r, "Could not set additional route: %m");
link_enter_failed(link);
return;
}
@@ -192,23 +308,303 @@ static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_a
link->ndisc_messages++;
}
-static void ndisc_handler(sd_ndisc *nd, int event, void *userdata) {
- Link *link = userdata;
+static void ndisc_rdnss_hash_func(const void *p, struct siphash *state) {
+ const NDiscRDNSS *x = p;
+
+ siphash24_compress(&x->address, sizeof(x->address), state);
+}
+
+static int ndisc_rdnss_compare_func(const void *_a, const void *_b) {
+ const NDiscRDNSS *a = _a, *b = _b;
+
+ return memcmp(&a->address, &b->address, sizeof(a->address));
+}
+
+static const struct hash_ops ndisc_rdnss_hash_ops = {
+ .hash = ndisc_rdnss_hash_func,
+ .compare = ndisc_rdnss_compare_func
+};
+
+static void ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) {
+ uint32_t lifetime;
+ const struct in6_addr *a;
+ usec_t time_now;
+ int i, n, r;
+
+ assert(link);
+ assert(rt);
+
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+ return;
+ }
+
+ r = sd_ndisc_router_rdnss_get_lifetime(rt, &lifetime);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m");
+ return;
+ }
+
+ n = sd_ndisc_router_rdnss_get_addresses(rt, &a);
+ if (n < 0) {
+ log_link_warning_errno(link, n, "Failed to get RDNSS addresses: %m");
+ return;
+ }
+
+ for (i = 0; i < n; i++) {
+ NDiscRDNSS d = {
+ .address = a[i]
+ }, *x;
+
+ if (lifetime == 0) {
+ (void) set_remove(link->ndisc_rdnss, &d);
+ link_dirty(link);
+ continue;
+ }
+
+ x = set_get(link->ndisc_rdnss, &d);
+ if (x) {
+ x->valid_until = time_now + lifetime * USEC_PER_SEC;
+ continue;
+ }
+
+ ndisc_vacuum(link);
+
+ if (set_size(link->ndisc_rdnss) >= NDISC_RDNSS_MAX) {
+ log_link_warning(link, "Too many RDNSS records per link, ignoring.");
+ continue;
+ }
+
+ r = set_ensure_allocated(&link->ndisc_rdnss, &ndisc_rdnss_hash_ops);
+ if (r < 0) {
+ log_oom();
+ return;
+ }
+
+ x = new0(NDiscRDNSS, 1);
+ if (!x) {
+ log_oom();
+ return;
+ }
+
+ x->address = a[i];
+ x->valid_until = time_now + lifetime * USEC_PER_SEC;
+
+ r = set_put(link->ndisc_rdnss, x);
+ if (r < 0) {
+ free(x);
+ log_oom();
+ return;
+ }
+
+ assert(r > 0);
+ link_dirty(link);
+ }
+}
+
+static void ndisc_dnssl_hash_func(const void *p, struct siphash *state) {
+ const NDiscDNSSL *x = p;
+
+ siphash24_compress(NDISC_DNSSL_DOMAIN(x), strlen(NDISC_DNSSL_DOMAIN(x)), state);
+}
+
+static int ndisc_dnssl_compare_func(const void *_a, const void *_b) {
+ const NDiscDNSSL *a = _a, *b = _b;
+
+ return strcmp(NDISC_DNSSL_DOMAIN(a), NDISC_DNSSL_DOMAIN(b));
+}
+
+static const struct hash_ops ndisc_dnssl_hash_ops = {
+ .hash = ndisc_dnssl_hash_func,
+ .compare = ndisc_dnssl_compare_func
+};
+
+static void ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) {
+ _cleanup_strv_free_ char **l = NULL;
+ uint32_t lifetime;
+ usec_t time_now;
+ char **i;
+ int r;
+
+ assert(link);
+ assert(rt);
+
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+ return;
+ }
+
+ r = sd_ndisc_router_dnssl_get_lifetime(rt, &lifetime);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m");
+ return;
+ }
+
+ r = sd_ndisc_router_dnssl_get_domains(rt, &l);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RDNSS addresses: %m");
+ return;
+ }
+
+ STRV_FOREACH(i, l) {
+ _cleanup_free_ NDiscDNSSL *s;
+ NDiscDNSSL *x;
+
+ s = malloc0(ALIGN(sizeof(NDiscDNSSL)) + strlen(*i) + 1);
+ if (!s) {
+ log_oom();
+ return;
+ }
+
+ strcpy(NDISC_DNSSL_DOMAIN(s), *i);
+
+ if (lifetime == 0) {
+ (void) set_remove(link->ndisc_dnssl, s);
+ link_dirty(link);
+ continue;
+ }
+
+ x = set_get(link->ndisc_dnssl, s);
+ if (x) {
+ x->valid_until = time_now + lifetime * USEC_PER_SEC;
+ continue;
+ }
+
+ ndisc_vacuum(link);
+
+ if (set_size(link->ndisc_dnssl) >= NDISC_DNSSL_MAX) {
+ log_link_warning(link, "Too many DNSSL records per link, ignoring.");
+ continue;
+ }
+
+ r = set_ensure_allocated(&link->ndisc_dnssl, &ndisc_dnssl_hash_ops);
+ if (r < 0) {
+ log_oom();
+ return;
+ }
+
+ s->valid_until = time_now + lifetime * USEC_PER_SEC;
+
+ r = set_put(link->ndisc_dnssl, s);
+ if (r < 0) {
+ log_oom();
+ return;
+ }
+
+ s = NULL;
+ assert(r > 0);
+ link_dirty(link);
+ }
+}
+
+static void ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
+ int r;
+
+ assert(link);
+ assert(rt);
+
+ r = sd_ndisc_router_option_rewind(rt);
+ for (;;) {
+ uint8_t type;
+
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to iterate through options: %m");
+ return;
+ }
+ if (r == 0) /* EOF */
+ break;
+
+ r = sd_ndisc_router_option_get_type(rt, &type);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA option type: %m");
+ return;
+ }
+
+ switch (type) {
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION: {
+ uint8_t flags;
+
+ r = sd_ndisc_router_prefix_get_flags(rt, &flags);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA prefix flags: %m");
+ return;
+ }
+
+ if (flags & ND_OPT_PI_FLAG_ONLINK)
+ ndisc_router_process_onlink_prefix(link, rt);
+ if (flags & ND_OPT_PI_FLAG_AUTO)
+ ndisc_router_process_autonomous_prefix(link, rt);
+
+ break;
+ }
+
+ case SD_NDISC_OPTION_ROUTE_INFORMATION:
+ ndisc_router_process_route(link, rt);
+ break;
+
+ case SD_NDISC_OPTION_RDNSS:
+ ndisc_router_process_rdnss(link, rt);
+ break;
+
+ case SD_NDISC_OPTION_DNSSL:
+ ndisc_router_process_dnssl(link, rt);
+ break;
+ }
+
+ r = sd_ndisc_router_option_next(rt);
+ }
+}
+
+static void ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
+ uint64_t flags;
int r;
assert(link);
+ assert(link->network);
+ assert(link->manager);
+ assert(rt);
+
+ r = sd_ndisc_router_get_flags(rt, &flags);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA flags: %m");
+ return;
+ }
+
+ if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)) {
+ /* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
+ r = dhcp6_request_address(link, !(flags & ND_RA_FLAG_MANAGED));
+ if (r < 0 && r != -EBUSY)
+ log_link_warning_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m");
+ else
+ log_link_debug(link, "Acquiring DHCPv6 lease on NDisc request");
+ }
+
+ ndisc_router_process_default(link, rt);
+ ndisc_router_process_options(link, rt);
+}
+
+static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) {
+ Link *link = userdata;
+
+ assert(link);
if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
return;
switch (event) {
+
+ case SD_NDISC_EVENT_ROUTER:
+ ndisc_router_handler(link, rt);
+ break;
+
case SD_NDISC_EVENT_TIMEOUT:
link->ndisc_configured = true;
link_check_ready(link);
break;
- case SD_NDISC_EVENT_STOP:
- break;
default:
log_link_warning(link, "IPv6 Neighbor Discovery unknown event: %d", event);
}
@@ -217,30 +613,52 @@ static void ndisc_handler(sd_ndisc *nd, int event, void *userdata) {
int ndisc_configure(Link *link) {
int r;
- assert_return(link, -EINVAL);
+ assert(link);
- r = sd_ndisc_new(&link->ndisc_router_discovery);
+ r = sd_ndisc_new(&link->ndisc);
if (r < 0)
return r;
- r = sd_ndisc_attach_event(link->ndisc_router_discovery, NULL, 0);
+ r = sd_ndisc_attach_event(link->ndisc, NULL, 0);
if (r < 0)
return r;
- r = sd_ndisc_set_mac(link->ndisc_router_discovery, &link->mac);
+ r = sd_ndisc_set_mac(link->ndisc, &link->mac);
if (r < 0)
return r;
- r = sd_ndisc_set_ifindex(link->ndisc_router_discovery, link->ifindex);
+ r = sd_ndisc_set_ifindex(link->ndisc, link->ifindex);
if (r < 0)
return r;
- r = sd_ndisc_set_callback(link->ndisc_router_discovery,
- ndisc_router_handler,
- ndisc_prefix_onlink_handler,
- ndisc_prefix_autonomous_handler,
- ndisc_handler,
- link);
+ r = sd_ndisc_set_callback(link->ndisc, ndisc_handler, link);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void ndisc_vacuum(Link *link) {
+ NDiscRDNSS *r;
+ NDiscDNSSL *d;
+ Iterator i;
+ usec_t time_now;
+
+ assert(link);
+
+ /* Removes all RDNSS and DNSSL entries whose validity time has passed */
+
+ time_now = now(clock_boottime_or_monotonic());
+
+ SET_FOREACH(r, link->ndisc_rdnss, i)
+ if (r->valid_until < time_now) {
+ (void) set_remove(link->ndisc_rdnss, r);
+ link_dirty(link);
+ }
- return r;
+ SET_FOREACH(d, link->ndisc_dnssl, i)
+ if (d->valid_until < time_now) {
+ (void) set_remove(link->ndisc_dnssl, d);
+ link_dirty(link);
+ }
}
diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h
new file mode 100644
index 0000000000..2002f55107
--- /dev/null
+++ b/src/network/networkd-ndisc.h
@@ -0,0 +1,39 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Tom Gundersen <teg@jklm.no>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "networkd-link.h"
+
+typedef struct NDiscRDNSS {
+ usec_t valid_until;
+ struct in6_addr address;
+} NDiscRDNSS;
+
+typedef struct NDiscDNSSL {
+ usec_t valid_until;
+ /* The domain name follows immediately. */
+} NDiscDNSSL;
+
+static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) {
+ return ((char*) n) + ALIGN(sizeof(NDiscDNSSL));
+}
+
+int ndisc_configure(Link *link);
+void ndisc_vacuum(Link *link);
diff --git a/src/network/networkd-netdev-gperf.gperf b/src/network/networkd-netdev-gperf.gperf
index adc64977b9..9d69f61376 100644
--- a/src/network/networkd-netdev-gperf.gperf
+++ b/src/network/networkd-netdev-gperf.gperf
@@ -3,6 +3,7 @@
#include "conf-parser.h"
#include "network-internal.h"
#include "networkd-netdev-bond.h"
+#include "networkd-netdev-bridge.h"
#include "networkd-netdev-ipvlan.h"
#include "networkd-netdev-macvlan.h"
#include "networkd-netdev-tunnel.h"
@@ -10,8 +11,9 @@
#include "networkd-netdev-veth.h"
#include "networkd-netdev-vlan.h"
#include "networkd-netdev-vxlan.h"
-#include "networkd-netdev-bridge.h"
+#include "networkd-netdev-vrf.h"
#include "networkd-netdev.h"
+#include "vlan-util.h"
%}
struct ConfigPerfItem;
%null_strings
@@ -33,7 +35,7 @@ NetDev.Name, config_parse_ifname, 0,
NetDev.Kind, config_parse_netdev_kind, 0, offsetof(NetDev, kind)
NetDev.MTUBytes, config_parse_iec_size, 0, offsetof(NetDev, mtu)
NetDev.MACAddress, config_parse_hwaddr, 0, offsetof(NetDev, mac)
-VLAN.Id, config_parse_uint64, 0, offsetof(VLan, id)
+VLAN.Id, config_parse_vlanid, 0, offsetof(VLan, id)
MACVLAN.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode)
MACVTAP.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode)
IPVLAN.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode)
@@ -41,6 +43,9 @@ Tunnel.Local, config_parse_tunnel_address, 0,
Tunnel.Remote, config_parse_tunnel_address, 0, offsetof(Tunnel, remote)
Tunnel.TOS, config_parse_unsigned, 0, offsetof(Tunnel, tos)
Tunnel.TTL, config_parse_unsigned, 0, offsetof(Tunnel, ttl)
+Tunnel.Key, config_parse_tunnel_key, 0, offsetof(Tunnel, key)
+Tunnel.InputKey, config_parse_tunnel_key, 0, offsetof(Tunnel, ikey)
+Tunnel.OutputKey, config_parse_tunnel_key, 0, offsetof(Tunnel, okey)
Tunnel.DiscoverPathMTU, config_parse_bool, 0, offsetof(Tunnel, pmtudisc)
Tunnel.Mode, config_parse_ip6tnl_mode, 0, offsetof(Tunnel, ip6tnl_mode)
Tunnel.IPv6FlowLabel, config_parse_ipv6_flowlabel, 0, offsetof(Tunnel, ipv6_flowlabel)
@@ -101,3 +106,4 @@ Bridge.ForwardDelaySec, config_parse_sec, 0,
Bridge.MulticastQuerier, config_parse_tristate, 0, offsetof(Bridge, mcast_querier)
Bridge.MulticastSnooping, config_parse_tristate, 0, offsetof(Bridge, mcast_snooping)
Bridge.VLANFiltering, config_parse_tristate, 0, offsetof(Bridge, vlan_filtering)
+VRF.TableId, config_parse_uint32, 0, offsetof(Vrf, table_id)
diff --git a/src/network/networkd-netdev-tunnel.c b/src/network/networkd-netdev-tunnel.c
index 7aaa041ba3..77a4734df8 100644
--- a/src/network/networkd-netdev-tunnel.c
+++ b/src/network/networkd-netdev-tunnel.c
@@ -35,7 +35,7 @@
#include "util.h"
#define DEFAULT_TNL_HOP_LIMIT 64
-#define IP6_FLOWINFO_FLOWLABEL htonl(0x000FFFFF)
+#define IP6_FLOWINFO_FLOWLABEL htobe32(0x000FFFFF)
static const char* const ip6tnl_mode_table[_NETDEV_IP6_TNL_MODE_MAX] = {
[NETDEV_IP6_TNL_MODE_IP6IP6] = "ip6ip6",
@@ -200,6 +200,33 @@ static int netdev_ip6gre_fill_message_create(NetDev *netdev, Link *link, sd_netl
return r;
}
+static int netdev_vti_fill_message_key(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Tunnel *t = VTI(netdev);
+ uint32_t ikey, okey;
+ int r;
+
+ assert(link);
+ assert(m);
+ assert(t);
+
+ if (t->key != 0)
+ ikey = okey = htobe32(t->key);
+ else {
+ ikey = htobe32(t->ikey);
+ okey = htobe32(t->okey);
+ }
+
+ r = sd_netlink_message_append_u32(m, IFLA_VTI_IKEY, ikey);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VTI_IKEY attribute: %m");
+
+ r = sd_netlink_message_append_u32(m, IFLA_VTI_OKEY, okey);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VTI_OKEY attribute: %m");
+
+ return 0;
+}
+
static int netdev_vti_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
Tunnel *t = VTI(netdev);
int r;
@@ -214,6 +241,10 @@ static int netdev_vti_fill_message_create(NetDev *netdev, Link *link, sd_netlink
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LINK attribute: %m");
+ r = netdev_vti_fill_message_key(netdev, link, m);
+ if (r < 0)
+ return r;
+
r = sd_netlink_message_append_in_addr(m, IFLA_VTI_LOCAL, &t->local.in);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LOCAL attribute: %m");
@@ -239,6 +270,10 @@ static int netdev_vti6_fill_message_create(NetDev *netdev, Link *link, sd_netlin
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LINK attribute: %m");
+ r = netdev_vti_fill_message_key(netdev, link, m);
+ if (r < 0)
+ return r;
+
r = sd_netlink_message_append_in6_addr(m, IFLA_VTI_LOCAL, &t->local.in6);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LOCAL attribute: %m");
@@ -413,6 +448,46 @@ int config_parse_tunnel_address(const char *unit,
return 0;
}
+int config_parse_tunnel_key(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ union in_addr_union buffer;
+ Tunnel *t = userdata;
+ uint32_t k;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = in_addr_from_string(AF_INET, rvalue, &buffer);
+ if (r < 0) {
+ r = safe_atou32(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse tunnel key ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ } else
+ k = be32toh(buffer.in.s_addr);
+
+ if (streq(lvalue, "Key"))
+ t->key = k;
+ else if (streq(lvalue, "InputKey"))
+ t->ikey = k;
+ else
+ t->okey = k;
+
+ return 0;
+}
+
int config_parse_ipv6_flowlabel(const char* unit,
const char *filename,
unsigned line,
@@ -444,7 +519,7 @@ int config_parse_ipv6_flowlabel(const char* unit,
if (k > 0xFFFFF)
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IPv6 flowlabel option, ignoring: %s", rvalue);
else {
- *ipv6_flowlabel = htonl(k) & IP6_FLOWINFO_FLOWLABEL;
+ *ipv6_flowlabel = htobe32(k) & IP6_FLOWINFO_FLOWLABEL;
t->flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL;
}
}
diff --git a/src/network/networkd-netdev-tunnel.h b/src/network/networkd-netdev-tunnel.h
index 7d31e7b687..32a46bd82f 100644
--- a/src/network/networkd-netdev-tunnel.h
+++ b/src/network/networkd-netdev-tunnel.h
@@ -49,6 +49,10 @@ typedef struct Tunnel {
unsigned tos;
unsigned flags;
+ uint32_t key;
+ uint32_t ikey;
+ uint32_t okey;
+
union in_addr_union local;
union in_addr_union remote;
@@ -108,3 +112,8 @@ int config_parse_encap_limit(const char *unit, const char *filename,
unsigned section_line, const char *lvalue,
int ltype, const char *rvalue, void *data,
void *userdata);
+int config_parse_tunnel_key(const char *unit, const char *filename,
+ unsigned line, const char *section,
+ unsigned section_line, const char *lvalue,
+ int ltype, const char *rvalue, void *data,
+ void *userdata);
diff --git a/src/network/networkd-netdev-vlan.c b/src/network/networkd-netdev-vlan.c
index b1f4714afa..3cc072388f 100644
--- a/src/network/networkd-netdev-vlan.c
+++ b/src/network/networkd-netdev-vlan.c
@@ -20,6 +20,7 @@
#include <net/if.h>
#include "networkd-netdev-vlan.h"
+#include "vlan-util.h"
static int netdev_vlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *req) {
VLan *v;
@@ -33,11 +34,9 @@ static int netdev_vlan_fill_message_create(NetDev *netdev, Link *link, sd_netlin
assert(v);
- if (v->id <= VLANID_MAX) {
- r = sd_netlink_message_append_u16(req, IFLA_VLAN_ID, v->id);
- if (r < 0)
- return log_netdev_error_errno(netdev, r, "Could not append IFLA_VLAN_ID attribute: %m");
- }
+ r = sd_netlink_message_append_u16(req, IFLA_VLAN_ID, v->id);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VLAN_ID attribute: %m");
return 0;
}
@@ -52,8 +51,8 @@ static int netdev_vlan_verify(NetDev *netdev, const char *filename) {
assert(v);
- if (v->id > VLANID_MAX) {
- log_warning("VLAN without valid Id (%"PRIu64") configured in %s. Ignoring", v->id, filename);
+ if (v->id == VLANID_INVALID) {
+ log_warning("VLAN without valid Id (%"PRIu16") configured in %s.", v->id, filename);
return -EINVAL;
}
@@ -66,7 +65,7 @@ static void vlan_init(NetDev *netdev) {
assert(netdev);
assert(v);
- v->id = VLANID_MAX + 1;
+ v->id = VLANID_INVALID;
}
const NetDevVTable vlan_vtable = {
diff --git a/src/network/networkd-netdev-vlan.h b/src/network/networkd-netdev-vlan.h
index 73aacf4a0f..2dfe314b6e 100644
--- a/src/network/networkd-netdev-vlan.h
+++ b/src/network/networkd-netdev-vlan.h
@@ -23,12 +23,10 @@ typedef struct VLan VLan;
#include "networkd-netdev.h"
-#define VLANID_MAX 4094
-
struct VLan {
NetDev meta;
- uint64_t id;
+ uint16_t id;
};
DEFINE_NETDEV_CAST(VLAN, VLan);
diff --git a/src/network/networkd-netdev-vrf.c b/src/network/networkd-netdev-vrf.c
new file mode 100644
index 0000000000..89bd142e8c
--- /dev/null
+++ b/src/network/networkd-netdev-vrf.c
@@ -0,0 +1,50 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Andreas Rammhold <andreas@rammhold.de>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <net/if.h>
+
+#include "sd-netlink.h"
+#include "missing.h"
+#include "networkd-netdev-vrf.h"
+
+static int netdev_vrf_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Vrf *v;
+ int r;
+
+ assert(netdev);
+ assert(!link);
+ assert(m);
+
+ v = VRF(netdev);
+
+ assert(v);
+
+ r = sd_netlink_message_append_u32(m, IFLA_VRF_TABLE, v->table_id);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IPLA_VRF_TABLE attribute: %m");
+
+ return r;
+}
+
+const NetDevVTable vrf_vtable = {
+ .object_size = sizeof(Vrf),
+ .sections = "NetDev\0VRF\0",
+ .fill_message_create = netdev_vrf_fill_message_create,
+ .create_type = NETDEV_CREATE_MASTER,
+};
diff --git a/src/network/networkd-netdev-vrf.h b/src/network/networkd-netdev-vrf.h
new file mode 100644
index 0000000000..3d92a26a4d
--- /dev/null
+++ b/src/network/networkd-netdev-vrf.h
@@ -0,0 +1,33 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Andreas Rammhold <andreas@rammhold.de>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Vrf Vrf;
+
+#include "networkd-netdev.h"
+
+struct Vrf {
+ NetDev meta;
+
+ uint32_t table_id;
+};
+
+DEFINE_NETDEV_CAST(VRF, Vrf);
+extern const NetDevVTable vrf_vtable;
diff --git a/src/network/networkd-netdev.c b/src/network/networkd-netdev.c
index 851a36290c..e7edc366af 100644
--- a/src/network/networkd-netdev.c
+++ b/src/network/networkd-netdev.c
@@ -55,6 +55,8 @@ const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = {
[NETDEV_KIND_TUN] = &tun_vtable,
[NETDEV_KIND_TAP] = &tap_vtable,
[NETDEV_KIND_IP6TNL] = &ip6tnl_vtable,
+ [NETDEV_KIND_VRF] = &vrf_vtable,
+
};
static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = {
@@ -78,6 +80,8 @@ static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = {
[NETDEV_KIND_TUN] = "tun",
[NETDEV_KIND_TAP] = "tap",
[NETDEV_KIND_IP6TNL] = "ip6tnl",
+ [NETDEV_KIND_VRF] = "vrf",
+
};
DEFINE_STRING_TABLE_LOOKUP(netdev_kind, NetDevKind);
@@ -198,7 +202,7 @@ static int netdev_enslave_ready(NetDev *netdev, Link* link, sd_netlink_message_h
assert(netdev->state == NETDEV_STATE_READY);
assert(netdev->manager);
assert(netdev->manager->rtnl);
- assert(IN_SET(netdev->kind, NETDEV_KIND_BRIDGE, NETDEV_KIND_BOND));
+ assert(IN_SET(netdev->kind, NETDEV_KIND_BRIDGE, NETDEV_KIND_BOND, NETDEV_KIND_VRF));
assert(link);
assert(callback);
@@ -281,7 +285,7 @@ int netdev_enslave(NetDev *netdev, Link *link, sd_netlink_message_handler_t call
assert(netdev);
assert(netdev->manager);
assert(netdev->manager->rtnl);
- assert(IN_SET(netdev->kind, NETDEV_KIND_BRIDGE, NETDEV_KIND_BOND));
+ assert(IN_SET(netdev->kind, NETDEV_KIND_BRIDGE, NETDEV_KIND_BOND, NETDEV_KIND_VRF));
if (netdev->state == NETDEV_STATE_READY) {
r = netdev_enslave_ready(netdev, link, callback);
@@ -615,7 +619,7 @@ static int netdev_load_one(Manager *manager, const char *filename) {
NULL, NULL, NULL, NULL, NULL, NULL) <= 0)
return 0;
- if (!NETDEV_VTABLE(netdev_raw)) {
+ if (netdev_raw->kind == _NETDEV_KIND_INVALID) {
log_warning("NetDev with invalid Kind configured in %s. Ignoring", filename);
return 0;
}
diff --git a/src/network/networkd-netdev.h b/src/network/networkd-netdev.h
index 20244c0309..b92a973b85 100644
--- a/src/network/networkd-netdev.h
+++ b/src/network/networkd-netdev.h
@@ -55,6 +55,7 @@ typedef enum NetDevKind {
NETDEV_KIND_DUMMY,
NETDEV_KIND_TUN,
NETDEV_KIND_TAP,
+ NETDEV_KIND_VRF,
_NETDEV_KIND_MAX,
_NETDEV_KIND_INVALID = -1
} NetDevKind;
diff --git a/src/network/networkd-network-bus.c b/src/network/networkd-network-bus.c
index d6b7448a43..6e21676d23 100644
--- a/src/network/networkd-network-bus.c
+++ b/src/network/networkd-network-bus.c
@@ -60,7 +60,7 @@ const sd_bus_vtable network_vtable[] = {
SD_BUS_PROPERTY("Description", "s", NULL, offsetof(Network, description), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SourcePath", "s", NULL, offsetof(Network, filename), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("MatchMAC", "as", property_get_ether_addrs, offsetof(Network, match_mac), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("MatchMAC", "as", property_get_ether_addrs, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MatchPath", "as", NULL, offsetof(Network, match_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MatchDriver", "as", NULL, offsetof(Network, match_driver), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MatchType", "as", NULL, offsetof(Network, match_type), SD_BUS_VTABLE_PROPERTY_CONST),
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 03e4e3b39f..19adac66b8 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -4,6 +4,7 @@
#include "networkd.h"
#include "networkd-conf.h"
#include "network-internal.h"
+#include "vlan-util.h"
%}
struct ConfigPerfItem;
%null_strings
@@ -27,6 +28,7 @@ Match.KernelCommandLine, config_parse_net_condition,
Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, match_arch)
Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac)
Link.MTUBytes, config_parse_iec_size, 0, offsetof(Network, mtu)
+Link.ARP, config_parse_tristate, 0, offsetof(Network, arp)
Network.Description, config_parse_string, 0, offsetof(Network, description)
Network.Bridge, config_parse_netdev, 0, offsetof(Network, bridge)
Network.Bond, config_parse_netdev, 0, offsetof(Network, bond)
@@ -36,6 +38,7 @@ Network.MACVTAP, config_parse_netdev,
Network.IPVLAN, config_parse_netdev, 0, 0
Network.VXLAN, config_parse_netdev, 0, 0
Network.Tunnel, config_parse_tunnel, 0, 0
+Network.VRF, config_parse_netdev, 0, 0
Network.DHCP, config_parse_dhcp, 0, offsetof(Network, dhcp)
Network.DHCPServer, config_parse_bool, 0, offsetof(Network, dhcp_server)
Network.LinkLocalAddressing, config_parse_address_family_boolean, 0, offsetof(Network, link_local)
@@ -50,11 +53,13 @@ Network.DNS, config_parse_strv,
Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr)
Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns)
Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode)
-Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, offsetof(Network, dnssec_negative_trust_anchors)
+Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, 0
Network.NTP, config_parse_strv, 0, offsetof(Network, ntp)
Network.IPForward, config_parse_address_family_boolean_with_kernel,0, offsetof(Network, ip_forward)
Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade)
Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Network, ipv6_privacy_extensions)
+Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra)
+/* legacy alias for the above */
Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra)
Network.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits)
Network.IPv6HopLimit, config_parse_int, 0, offsetof(Network, ipv6_hop_limit)
@@ -89,6 +94,8 @@ DHCP.DUIDRawData, config_parse_duid_rawdata,
DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric)
DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone)
DHCP.IAID, config_parse_iaid, 0, offsetof(Network, iaid)
+IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns)
+IPv6AcceptRA.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains)
DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec)
DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec)
DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_dns)
@@ -108,6 +115,9 @@ Bridge.AllowPortToBeRoot, config_parse_bool,
Bridge.UnicastFlood, config_parse_bool, 0, offsetof(Network, unicast_flood)
BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0
BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0
+BridgeVLAN.PVID, config_parse_vlanid, 0, offsetof(Network, pvid)
+BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0
+BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0
/* backwards compatibility: do not add new entries to this section */
Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index dd89b3770c..17bbe5de9f 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -51,8 +51,8 @@ static int network_load_one(Manager *manager, const char *filename) {
if (!file) {
if (errno == ENOENT)
return 0;
- else
- return -errno;
+
+ return -errno;
}
if (null_or_empty_fd(fileno(file))) {
@@ -134,6 +134,8 @@ static int network_load_one(Manager *manager, const char *filename) {
network->ipv6_hop_limit = -1;
network->duid.type = _DUID_TYPE_INVALID;
network->proxy_arp = -1;
+ network->arp = -1;
+ network->ipv6_accept_ra_use_dns = true;
r = config_parse(NULL, filename, file,
"Match\0"
@@ -144,8 +146,10 @@ static int network_load_one(Manager *manager, const char *filename) {
"DHCP\0"
"DHCPv4\0" /* compat */
"DHCPServer\0"
+ "IPv6AcceptRA\0"
"Bridge\0"
- "BridgeFDB\0",
+ "BridgeFDB\0"
+ "BridgeVLAN\0",
config_item_perf_lookup, network_network_gperf_lookup,
false, false, true, network);
if (r < 0)
@@ -241,8 +245,8 @@ void network_free(Network *network) {
strv_free(network->bind_carrier);
netdev_unref(network->bridge);
-
netdev_unref(network->bond);
+ netdev_unref(network->vrf);
HASHMAP_FOREACH(netdev, network->stacked_netdevs, i) {
hashmap_remove(network->stacked_netdevs, netdev->ifname);
@@ -468,6 +472,10 @@ int config_parse_netdev(const char *unit,
network->bond = netdev;
break;
+ case NETDEV_KIND_VRF:
+ network->vrf = netdev;
+
+ break;
case NETDEV_KIND_VLAN:
case NETDEV_KIND_MACVLAN:
case NETDEV_KIND_MACVTAP:
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 91099161ce..7c0bdc1e4a 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -28,6 +28,7 @@
#include "resolve-util.h"
#include "networkd-address.h"
+#include "networkd-brvlan.h"
#include "networkd-fdb.h"
#include "networkd-lldp-tx.h"
#include "networkd-netdev.h"
@@ -37,6 +38,9 @@
#define DHCP_ROUTE_METRIC 1024
#define IPV4LL_ROUTE_METRIC 2048
+#define BRIDGE_VLAN_BITMAP_MAX 4096
+#define BRIDGE_VLAN_BITMAP_LEN (BRIDGE_VLAN_BITMAP_MAX / 32)
+
typedef enum DCHPClientIdentifier {
DHCP_CLIENT_ID_MAC,
DHCP_CLIENT_ID_DUID,
@@ -100,6 +104,7 @@ struct Network {
NetDev *bridge;
NetDev *bond;
+ NetDev *vrf;
Hashmap *stacked_netdevs;
/* DHCP Client Support */
@@ -146,6 +151,10 @@ struct Network {
bool unicast_flood;
unsigned cost;
+ uint16_t pvid;
+ uint32_t br_vid_bitmap[BRIDGE_VLAN_BITMAP_LEN];
+ uint32_t br_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN];
+
AddressFamilyBoolean ip_forward;
bool ip_masquerade;
@@ -154,11 +163,15 @@ struct Network {
int ipv6_hop_limit;
int proxy_arp;
+ bool ipv6_accept_ra_use_dns;
+ DHCPUseDomains ipv6_accept_ra_use_domains;
+
union in_addr_union ipv6_token;
IPv6PrivacyExtensions ipv6_privacy_extensions;
struct ether_addr *mac;
unsigned mtu;
+ int arp;
uint32_t iaid;
DUID duid;
@@ -169,6 +182,10 @@ struct Network {
LIST_HEAD(Route, static_routes);
LIST_HEAD(FdbEntry, static_fdb_entries);
+ unsigned n_static_addresses;
+ unsigned n_static_routes;
+ unsigned n_static_fdb_entries;
+
Hashmap *addresses_by_section;
Hashmap *routes_by_section;
Hashmap *fdb_entries_by_section;
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
index f001de772a..cedaf47cf8 100644
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -28,6 +28,9 @@
#include "string-util.h"
#include "util.h"
+#define ROUTES_PER_LINK_MAX 2048U
+#define STATIC_ROUTES_PER_NETWORK_MAX 1024U
+
int route_new(Route **ret) {
_cleanup_route_free_ Route *route = NULL;
@@ -51,6 +54,9 @@ int route_new_static(Network *network, unsigned section, Route **ret) {
_cleanup_route_free_ Route *route = NULL;
int r;
+ assert(network);
+ assert(ret);
+
if (section) {
route = hashmap_get(network->routes_by_section, UINT_TO_PTR(section));
if (route) {
@@ -61,6 +67,9 @@ int route_new_static(Network *network, unsigned section, Route **ret) {
}
}
+ if (network->n_static_routes >= STATIC_ROUTES_PER_NETWORK_MAX)
+ return -E2BIG;
+
r = route_new(&route);
if (r < 0)
return r;
@@ -77,6 +86,7 @@ int route_new_static(Network *network, unsigned section, Route **ret) {
route->network = network;
LIST_PREPEND(routes, network->static_routes, route);
+ network->n_static_routes++;
*ret = route;
route = NULL;
@@ -91,9 +101,11 @@ void route_free(Route *route) {
if (route->network) {
LIST_REMOVE(routes, route->network->static_routes, route);
+ assert(route->network->n_static_routes > 0);
+ route->network->n_static_routes--;
+
if (route->section)
- hashmap_remove(route->network->routes_by_section,
- UINT_TO_PTR(route->section));
+ hashmap_remove(route->network->routes_by_section, UINT_TO_PTR(route->section));
}
if (route->link) {
@@ -176,48 +188,55 @@ static const struct hash_ops route_hash_ops = {
int route_get(Link *link,
int family,
- union in_addr_union *dst,
+ const union in_addr_union *dst,
unsigned char dst_prefixlen,
unsigned char tos,
uint32_t priority,
unsigned char table,
Route **ret) {
- Route route = {
+
+ Route route, *existing;
+
+ assert(link);
+ assert(dst);
+
+ route = (Route) {
.family = family,
+ .dst = *dst,
.dst_prefixlen = dst_prefixlen,
.tos = tos,
.priority = priority,
.table = table,
- }, *existing;
-
- assert(link);
- assert(dst);
- assert(ret);
-
- route.dst = *dst;
+ };
existing = set_get(link->routes, &route);
if (existing) {
- *ret = existing;
+ if (ret)
+ *ret = existing;
return 1;
- } else {
- existing = set_get(link->routes_foreign, &route);
- if (!existing)
- return -ENOENT;
}
- *ret = existing;
+ existing = set_get(link->routes_foreign, &route);
+ if (existing) {
+ if (ret)
+ *ret = existing;
+ return 0;
+ }
- return 0;
+ return -ENOENT;
}
-static int route_add_internal(Link *link, Set **routes,
- int family,
- union in_addr_union *dst,
- unsigned char dst_prefixlen,
- unsigned char tos,
- uint32_t priority,
- unsigned char table, Route **ret) {
+static int route_add_internal(
+ Link *link,
+ Set **routes,
+ int family,
+ const union in_addr_union *dst,
+ unsigned char dst_prefixlen,
+ unsigned char tos,
+ uint32_t priority,
+ unsigned char table,
+ Route **ret) {
+
_cleanup_route_free_ Route *route = NULL;
int r;
@@ -254,23 +273,29 @@ static int route_add_internal(Link *link, Set **routes,
return 0;
}
-int route_add_foreign(Link *link,
- int family,
- union in_addr_union *dst,
- unsigned char dst_prefixlen,
- unsigned char tos,
- uint32_t priority,
- unsigned char table, Route **ret) {
+int route_add_foreign(
+ Link *link,
+ int family,
+ const union in_addr_union *dst,
+ unsigned char dst_prefixlen,
+ unsigned char tos,
+ uint32_t priority,
+ unsigned char table,
+ Route **ret) {
+
return route_add_internal(link, &link->routes_foreign, family, dst, dst_prefixlen, tos, priority, table, ret);
}
-int route_add(Link *link,
+int route_add(
+ Link *link,
int family,
- union in_addr_union *dst,
+ const union in_addr_union *dst,
unsigned char dst_prefixlen,
unsigned char tos,
uint32_t priority,
- unsigned char table, Route **ret) {
+ unsigned char table,
+ Route **ret) {
+
Route *route;
int r;
@@ -303,12 +328,13 @@ int route_add(Link *link,
}
int route_update(Route *route,
- union in_addr_union *src,
+ const union in_addr_union *src,
unsigned char src_prefixlen,
- union in_addr_union *gw,
- union in_addr_union *prefsrc,
+ const union in_addr_union *gw,
+ const union in_addr_union *prefsrc,
unsigned char scope,
unsigned char protocol) {
+
assert(route);
assert(src);
assert(gw);
@@ -449,8 +475,11 @@ int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) {
return 1;
}
-int route_configure(Route *route, Link *link,
- sd_netlink_message_handler_t callback) {
+int route_configure(
+ Route *route,
+ Link *link,
+ sd_netlink_message_handler_t callback) {
+
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
_cleanup_(sd_event_source_unrefp) sd_event_source *expire = NULL;
usec_t lifetime;
@@ -462,6 +491,10 @@ int route_configure(Route *route, Link *link,
assert(link->ifindex > 0);
assert(route->family == AF_INET || route->family == AF_INET6);
+ if (route_get(link, route->family, &route->dst, route->dst_prefixlen, route->tos, route->priority, route->table, NULL) <= 0 &&
+ set_size(link->routes) >= ROUTES_PER_LINK_MAX)
+ return -E2BIG;
+
r = sd_rtnl_message_new_route(link->manager->rtnl, &req,
RTM_NEWROUTE, route->family,
route->protocol);
@@ -762,6 +795,7 @@ int config_parse_route_priority(const char *unit,
void *userdata) {
Network *network = userdata;
_cleanup_route_free_ Route *n = NULL;
+ uint32_t k;
int r;
assert(filename);
@@ -774,12 +808,14 @@ int config_parse_route_priority(const char *unit,
if (r < 0)
return r;
- r = config_parse_uint32(unit, filename, line, section,
- section_line, lvalue, ltype,
- rvalue, &n->priority, userdata);
- if (r < 0)
- return r;
+ r = safe_atou32(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Could not parse route priority \"%s\", ignoring assignment: %m", rvalue);
+ return 0;
+ }
+ n->priority = k;
n = NULL;
return 0;
diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h
index 39de8363ed..d4e4dbac0b 100644
--- a/src/network/networkd-route.h
+++ b/src/network/networkd-route.h
@@ -57,10 +57,10 @@ void route_free(Route *route);
int route_configure(Route *route, Link *link, sd_netlink_message_handler_t callback);
int route_remove(Route *route, Link *link, sd_netlink_message_handler_t callback);
-int route_get(Link *link, int family, union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, unsigned char table, Route **ret);
-int route_add(Link *link, int family, union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, unsigned char table, Route **ret);
-int route_add_foreign(Link *link, int family, union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, unsigned char table, Route **ret);
-int route_update(Route *route, union in_addr_union *src, unsigned char src_prefixlen, union in_addr_union *gw, union in_addr_union *prefsrc, unsigned char scope, unsigned char protocol);
+int route_get(Link *link, int family, const union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, unsigned char table, Route **ret);
+int route_add(Link *link, int family, const union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, unsigned char table, Route **ret);
+int route_add_foreign(Link *link, int family, const union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, unsigned char table, Route **ret);
+int route_update(Route *route, const union in_addr_union *src, unsigned char src_prefixlen, const union in_addr_union *gw, const union in_addr_union *prefsrc, unsigned char scope, unsigned char protocol);
int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata);
diff --git a/src/network/networkd-wait-online-manager.c b/src/network/networkd-wait-online-manager.c
index 2ff7ddb044..725b3310dd 100644
--- a/src/network/networkd-wait-online-manager.c
+++ b/src/network/networkd-wait-online-manager.c
@@ -30,8 +30,6 @@
#include "util.h"
bool manager_ignore_link(Manager *m, Link *link) {
- char **ignore;
-
assert(m);
assert(link);
@@ -44,11 +42,7 @@ bool manager_ignore_link(Manager *m, Link *link) {
return true;
/* ignore interfaces we explicitly are asked to ignore */
- STRV_FOREACH(ignore, m->ignore)
- if (fnmatch(*ignore, link->ifname, 0) == 0)
- return true;
-
- return false;
+ return strv_fnmatch(m->ignore, link->ifname, 0);
}
bool manager_all_configured(Manager *m) {
diff --git a/src/network/networkd.h b/src/network/networkd.h
index ab512f0d08..c4bd712147 100644
--- a/src/network/networkd.h
+++ b/src/network/networkd.h
@@ -41,6 +41,7 @@
#include "networkd-netdev-tuntap.h"
#include "networkd-netdev-veth.h"
#include "networkd-netdev-vlan.h"
+#include "networkd-netdev-vrf.h"
#include "networkd-netdev-vxlan.h"
#include "networkd-network.h"
#include "networkd-util.h"
diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c
index f50f1ad6c2..b1580236c9 100644
--- a/src/nspawn/nspawn-cgroup.c
+++ b/src/nspawn/nspawn-cgroup.c
@@ -123,7 +123,7 @@ int create_subcgroup(pid_t pid, bool unified_requested) {
int unified, r;
CGroupMask supported;
- /* In the unified hierarchy inner nodes may only only contain
+ /* In the unified hierarchy inner nodes may only contain
* subgroups, but not processes. Hence, if we running in the
* unified hierarchy and the container does the same, and we
* did not create a scope unit for the container move us and
diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf
index 2b5d452662..3231a48d5a 100644
--- a/src/nspawn/nspawn-gperf.gperf
+++ b/src/nspawn/nspawn-gperf.gperf
@@ -27,6 +27,7 @@ Exec.Personality, config_parse_personality, 0, offsetof(Settings,
Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id)
Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory)
Exec.PrivateUsers, config_parse_private_users, 0, 0
+Exec.NotifyReady, config_parse_bool, 0, offsetof(Settings, notify_ready)
Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only)
Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode)
Files.Bind, config_parse_bind, 0, 0
diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c
index 8e2d2d543c..803caef3dd 100644
--- a/src/nspawn/nspawn-mount.c
+++ b/src/nspawn/nspawn-mount.c
@@ -23,6 +23,8 @@
#include "alloc-util.h"
#include "cgroup-util.h"
#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
#include "fs-util.h"
#include "label.h"
#include "mkdir.h"
@@ -181,13 +183,15 @@ int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) {
static int tmpfs_patch_options(
const char *options,
- bool userns, uid_t uid_shift, uid_t uid_range,
+ bool userns,
+ uid_t uid_shift, uid_t uid_range,
+ bool patch_ids,
const char *selinux_apifs_context,
char **ret) {
char *buf = NULL;
- if (userns && uid_shift != 0) {
+ if ((userns && uid_shift != 0) || patch_ids) {
assert(uid_shift != UID_INVALID);
if (options)
@@ -218,7 +222,13 @@ static int tmpfs_patch_options(
}
#endif
+ if (!buf && options) {
+ buf = strdup(options);
+ if (!buf)
+ return -ENOMEM;
+ }
*ret = buf;
+
return !!buf;
}
@@ -271,7 +281,15 @@ int mount_sysfs(const char *dest) {
return log_error_errno(errno, "Failed to remove %s: %m", full);
x = prefix_roota(top, "/fs/kdbus");
- (void) mkdir(x, 0755);
+ (void) mkdir_p(x, 0755);
+
+ /* Create mountpoint for cgroups. Otherwise we are not allowed since we
+ * remount /sys read-only.
+ */
+ if (cg_ns_supported()) {
+ x = prefix_roota(top, "/fs/cgroup");
+ (void) mkdir_p(x, 0755);
+ }
if (mount(NULL, top, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL) < 0)
return log_error_errno(errno, "Failed to make %s read-only: %m", top);
@@ -297,18 +315,19 @@ int mount_all(const char *dest,
} MountPoint;
static const MountPoint mount_table[] = {
- { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true, true, false },
- { "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, true, true, false }, /* Bind mount first */
- { NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, true, true, false }, /* Then, make it r/o */
- { "tmpfs", "/sys", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, true },
- { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, false },
- { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, true, false, false },
- { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false },
- { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false },
- { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, true, false, false },
+ { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true, true, false },
+ { "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, true, true, false }, /* Bind mount first ...*/
+ { "/proc/sys/net", "/proc/sys/net", NULL, NULL, MS_BIND, true, true, true }, /* (except for this) */
+ { NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, true, true, false }, /* ... then, make it r/o */
+ { "tmpfs", "/sys", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, true },
+ { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false, false },
+ { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, true, false, false },
+ { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false },
+ { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false, false },
+ { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, true, false, false },
#ifdef HAVE_SELINUX
- { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, false, false, false }, /* Bind mount first */
- { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, false, false }, /* Then, make it r/o */
+ { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, false, false, false }, /* Bind mount first */
+ { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, false, false }, /* Then, make it r/o */
#endif
};
@@ -342,13 +361,13 @@ int mount_all(const char *dest,
if (mount_table[k].fatal)
return log_error_errno(r, "Failed to create directory %s: %m", where);
- log_warning_errno(r, "Failed to create directory %s: %m", where);
+ log_debug_errno(r, "Failed to create directory %s: %m", where);
continue;
}
o = mount_table[k].options;
if (streq_ptr(mount_table[k].type, "tmpfs")) {
- r = tmpfs_patch_options(o, use_userns, uid_shift, uid_range, selinux_apifs_context, &options);
+ r = tmpfs_patch_options(o, use_userns, uid_shift, uid_range, false, selinux_apifs_context, &options);
if (r < 0)
return log_oom();
if (r > 0)
@@ -485,7 +504,7 @@ static int mount_tmpfs(
if (r < 0 && r != -EEXIST)
return log_error_errno(r, "Creating mount point for tmpfs %s failed: %m", where);
- r = tmpfs_patch_options(m->options, userns, uid_shift, uid_range, selinux_apifs_context, &buf);
+ r = tmpfs_patch_options(m->options, userns, uid_shift, uid_range, false, selinux_apifs_context, &buf);
if (r < 0)
return log_oom();
options = r > 0 ? buf : m->options;
@@ -600,6 +619,48 @@ int mount_custom(
return 0;
}
+/* Retrieve existing subsystems. This function is called in a new cgroup
+ * namespace.
+ */
+static int get_controllers(Set *subsystems) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX];
+
+ assert(subsystems);
+
+ f = fopen("/proc/self/cgroup", "re");
+ if (!f)
+ return errno == ENOENT ? -ESRCH : -errno;
+
+ FOREACH_LINE(line, f, return -errno) {
+ int r;
+ char *e, *l, *p;
+
+ truncate_nl(line);
+
+ l = strchr(line, ':');
+ if (!l)
+ continue;
+
+ l++;
+ e = strchr(l, ':');
+ if (!e)
+ continue;
+
+ *e = 0;
+
+ if (streq(l, "") || streq(l, "name=systemd"))
+ continue;
+
+ p = strdup(l);
+ r = set_consume(subsystems, p);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controller, const char *hierarchy, bool read_only) {
char *to;
int r;
@@ -628,11 +689,107 @@ static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controlle
return 1;
}
-static int mount_legacy_cgroups(
- const char *dest,
+/* Mount a legacy cgroup hierarchy when cgroup namespaces are supported. */
+static int mount_legacy_cgns_supported(
bool userns, uid_t uid_shift, uid_t uid_range,
const char *selinux_apifs_context) {
+ _cleanup_set_free_free_ Set *controllers = NULL;
+ const char *cgroup_root = "/sys/fs/cgroup", *c;
+ int r;
+
+ (void) mkdir_p(cgroup_root, 0755);
+
+ /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */
+ r = path_is_mount_point(cgroup_root, AT_SYMLINK_FOLLOW);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m");
+ if (r == 0) {
+ _cleanup_free_ char *options = NULL;
+
+ /* When cgroup namespaces are enabled and user namespaces are
+ * used then the mount of the cgroupfs is done *inside* the new
+ * user namespace. We're root in the new user namespace and the
+ * kernel will happily translate our uid/gid to the correct
+ * uid/gid as seen from e.g. /proc/1/mountinfo. So we simply
+ * pass uid 0 and not uid_shift to tmpfs_patch_options().
+ */
+ r = tmpfs_patch_options("mode=755", userns, 0, uid_range, true, selinux_apifs_context, &options);
+ if (r < 0)
+ return log_oom();
+ if (mount("tmpfs", cgroup_root, "tmpfs", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, options) < 0)
+ return log_error_errno(errno, "Failed to mount /sys/fs/cgroup: %m");
+ }
+
+ if (cg_unified() > 0)
+ goto skip_controllers;
+
+ controllers = set_new(&string_hash_ops);
+ if (!controllers)
+ return log_oom();
+
+ r = get_controllers(controllers);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine cgroup controllers: %m");
+
+ for (;;) {
+ _cleanup_free_ const char *controller = NULL;
+
+ controller = set_steal_first(controllers);
+ if (!controller)
+ break;
+
+ r = mount_legacy_cgroup_hierarchy("", controller, controller, !userns);
+ if (r < 0)
+ return r;
+
+ /* When multiple hierarchies are co-mounted, make their
+ * constituting individual hierarchies a symlink to the
+ * co-mount.
+ */
+ c = controller;
+ for (;;) {
+ _cleanup_free_ char *target = NULL, *tok = NULL;
+
+ r = extract_first_word(&c, &tok, ",", 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to extract co-mounted cgroup controller: %m");
+ if (r == 0)
+ break;
+
+ target = prefix_root("/sys/fs/cgroup", tok);
+ if (!target)
+ return log_oom();
+
+ if (streq(controller, tok))
+ break;
+
+ r = symlink_idempotent(controller, target);
+ if (r == -EINVAL)
+ return log_error_errno(r, "Invalid existing symlink for combined hierarchy: %m");
+ if (r < 0)
+ return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m");
+ }
+ }
+
+skip_controllers:
+ r = mount_legacy_cgroup_hierarchy("", "none,name=systemd,xattr", "systemd", false);
+ if (r < 0)
+ return r;
+
+ if (!userns) {
+ if (mount(NULL, cgroup_root, NULL, MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755") < 0)
+ return log_error_errno(errno, "Failed to remount %s read-only: %m", cgroup_root);
+ }
+
+ return 0;
+}
+
+/* Mount legacy cgroup hierarchy when cgroup namespaces are unsupported. */
+static int mount_legacy_cgns_unsupported(
+ const char *dest,
+ bool userns, uid_t uid_shift, uid_t uid_range,
+ const char *selinux_apifs_context) {
_cleanup_set_free_free_ Set *controllers = NULL;
const char *cgroup_root;
int r;
@@ -648,7 +805,7 @@ static int mount_legacy_cgroups(
if (r == 0) {
_cleanup_free_ char *options = NULL;
- r = tmpfs_patch_options("mode=755", userns, uid_shift, uid_range, selinux_apifs_context, &options);
+ r = tmpfs_patch_options("mode=755", userns, uid_shift, uid_range, false, selinux_apifs_context, &options);
if (r < 0)
return log_oom();
@@ -707,10 +864,8 @@ static int mount_legacy_cgroups(
return r;
r = symlink_idempotent(combined, target);
- if (r == -EINVAL) {
- log_error("Invalid existing symlink for combined hierarchy");
- return r;
- }
+ if (r == -EINVAL)
+ return log_error_errno(r, "Invalid existing symlink for combined hierarchy: %m");
if (r < 0)
return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m");
}
@@ -761,12 +916,15 @@ int mount_cgroups(
const char *dest,
bool unified_requested,
bool userns, uid_t uid_shift, uid_t uid_range,
- const char *selinux_apifs_context) {
+ const char *selinux_apifs_context,
+ bool use_cgns) {
if (unified_requested)
return mount_unified_cgroups(dest);
- else
- return mount_legacy_cgroups(dest, userns, uid_shift, uid_range, selinux_apifs_context);
+ else if (use_cgns && cg_ns_supported())
+ return mount_legacy_cgns_supported(userns, uid_shift, uid_range, selinux_apifs_context);
+
+ return mount_legacy_cgns_unsupported(dest, userns, uid_shift, uid_range, selinux_apifs_context);
}
int mount_systemd_cgroup_writable(
@@ -834,7 +992,7 @@ int setup_volatile_state(
return log_error_errno(errno, "Failed to create %s: %m", directory);
options = "mode=755";
- r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf);
+ r = tmpfs_patch_options(options, userns, uid_shift, uid_range, false, selinux_apifs_context, &buf);
if (r < 0)
return log_oom();
if (r > 0)
@@ -870,7 +1028,7 @@ int setup_volatile(
return log_error_errno(errno, "Failed to create temporary directory: %m");
options = "mode=755";
- r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf);
+ r = tmpfs_patch_options(options, userns, uid_shift, uid_range, false, selinux_apifs_context, &buf);
if (r < 0)
return log_oom();
if (r > 0)
diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h
index 0daf145412..0eff8e1006 100644
--- a/src/nspawn/nspawn-mount.h
+++ b/src/nspawn/nspawn-mount.h
@@ -58,7 +58,7 @@ int custom_mount_compare(const void *a, const void *b);
int mount_all(const char *dest, bool use_userns, bool in_userns, bool use_netns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context);
int mount_sysfs(const char *dest);
-int mount_cgroups(const char *dest, bool unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context);
+int mount_cgroups(const char *dest, bool unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context, bool use_cgns);
int mount_systemd_cgroup_writable(const char *dest, bool unified_requested);
int mount_custom(const char *dest, CustomMount *mounts, unsigned n, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context);
diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c
index 8da47a2ca6..428cc04de0 100644
--- a/src/nspawn/nspawn-network.c
+++ b/src/nspawn/nspawn-network.c
@@ -350,7 +350,7 @@ int setup_bridge(const char *veth_name, const char *bridge_name, bool create) {
if (create) {
/* We take a system-wide lock here, so that we can safely check whether there's still a member in the
- * bridge before removing it, without risking interferance from other nspawn instances. */
+ * bridge before removing it, without risking interference from other nspawn instances. */
r = make_lock_file("/run/systemd/nspawn-network-zone", LOCK_EX, &bridge_lock);
if (r < 0)
diff --git a/src/nspawn/nspawn-patch-uid.c b/src/nspawn/nspawn-patch-uid.c
index cc79597c95..ded5866d05 100644
--- a/src/nspawn/nspawn-patch-uid.c
+++ b/src/nspawn/nspawn-patch-uid.c
@@ -263,9 +263,12 @@ static int patch_fd(int fd, const char *name, const struct stat *st, uid_t shift
return -errno;
/* The Linux kernel alters the mode in some cases of chown(). Let's undo this. */
- if (name && !S_ISLNK(st->st_mode))
- r = fchmodat(fd, name, st->st_mode, 0);
- else
+ if (name) {
+ if (!S_ISLNK(st->st_mode))
+ r = fchmodat(fd, name, st->st_mode, 0);
+ else /* AT_SYMLINK_NOFOLLOW is not available for fchmodat() */
+ r = 0;
+ } else
r = fchmod(fd, st->st_mode);
if (r < 0)
return -errno;
diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c
index 20103c5e88..e5b76a0c5d 100644
--- a/src/nspawn/nspawn-register.c
+++ b/src/nspawn/nspawn-register.c
@@ -104,7 +104,7 @@ int register_machine(
return bus_log_create_error(r);
}
- r = sd_bus_message_append(m, "(sv)", "DevicePolicy", "s", "strict");
+ r = sd_bus_message_append(m, "(sv)", "DevicePolicy", "s", "closed");
if (r < 0)
return bus_log_create_error(r);
@@ -112,26 +112,19 @@ int register_machine(
* systemd-nspawn@.service, to keep the device
* policies in sync regardless if we are run with or
* without the --keep-unit switch. */
- r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 9,
+ r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 2,
/* Allow the container to
* access and create the API
* device nodes, so that
* PrivateDevices= in the
* container can work
* fine */
- "/dev/null", "rwm",
- "/dev/zero", "rwm",
- "/dev/full", "rwm",
- "/dev/random", "rwm",
- "/dev/urandom", "rwm",
- "/dev/tty", "rwm",
"/dev/net/tun", "rwm",
/* Allow the container
* access to ptys. However,
* do not permit the
* container to ever create
* these device nodes. */
- "/dev/pts/ptmx", "rw",
"char-pts", "rw");
if (r < 0)
return bus_log_create_error(r);
diff --git a/src/nspawn/nspawn-seccomp.c b/src/nspawn/nspawn-seccomp.c
index 2d145b68a7..3ab7160ebe 100644
--- a/src/nspawn/nspawn-seccomp.c
+++ b/src/nspawn/nspawn-seccomp.c
@@ -44,29 +44,83 @@ static int seccomp_add_default_syscall_filter(scmp_filter_ctx ctx,
uint64_t capability;
int syscall_num;
} blacklist[] = {
- { CAP_SYS_RAWIO, SCMP_SYS(iopl) },
- { CAP_SYS_RAWIO, SCMP_SYS(ioperm) },
- { CAP_SYS_BOOT, SCMP_SYS(kexec_load) },
- { CAP_SYS_ADMIN, SCMP_SYS(swapon) },
- { CAP_SYS_ADMIN, SCMP_SYS(swapoff) },
- { CAP_SYS_ADMIN, SCMP_SYS(open_by_handle_at) },
- { CAP_SYS_MODULE, SCMP_SYS(init_module) },
- { CAP_SYS_MODULE, SCMP_SYS(finit_module) },
- { CAP_SYS_MODULE, SCMP_SYS(delete_module) },
- { CAP_SYSLOG, SCMP_SYS(syslog) },
+ { 0, SCMP_SYS(_sysctl) }, /* obsolete syscall */
+ { 0, SCMP_SYS(add_key) }, /* keyring is not namespaced */
+ { 0, SCMP_SYS(afs_syscall) }, /* obsolete syscall */
+ { 0, SCMP_SYS(bdflush) },
+#ifdef __NR_bpf
+ { 0, SCMP_SYS(bpf) },
+#endif
+ { 0, SCMP_SYS(break) }, /* obsolete syscall */
+ { 0, SCMP_SYS(create_module) }, /* obsolete syscall */
+ { 0, SCMP_SYS(ftime) }, /* obsolete syscall */
+ { 0, SCMP_SYS(get_kernel_syms) }, /* obsolete syscall */
+ { 0, SCMP_SYS(getpmsg) }, /* obsolete syscall */
+ { 0, SCMP_SYS(gtty) }, /* obsolete syscall */
+#ifdef __NR_kexec_file_load
+ { 0, SCMP_SYS(kexec_file_load) },
+#endif
+ { 0, SCMP_SYS(kexec_load) },
+ { 0, SCMP_SYS(keyctl) }, /* keyring is not namespaced */
+ { 0, SCMP_SYS(lock) }, /* obsolete syscall */
+ { 0, SCMP_SYS(lookup_dcookie) },
+ { 0, SCMP_SYS(mpx) }, /* obsolete syscall */
+ { 0, SCMP_SYS(nfsservctl) }, /* obsolete syscall */
+ { 0, SCMP_SYS(open_by_handle_at) },
+ { 0, SCMP_SYS(perf_event_open) },
+ { 0, SCMP_SYS(prof) }, /* obsolete syscall */
+ { 0, SCMP_SYS(profil) }, /* obsolete syscall */
+ { 0, SCMP_SYS(putpmsg) }, /* obsolete syscall */
+ { 0, SCMP_SYS(query_module) }, /* obsolete syscall */
+ { 0, SCMP_SYS(quotactl) },
+ { 0, SCMP_SYS(request_key) }, /* keyring is not namespaced */
+ { 0, SCMP_SYS(security) }, /* obsolete syscall */
+ { 0, SCMP_SYS(sgetmask) }, /* obsolete syscall */
+ { 0, SCMP_SYS(ssetmask) }, /* obsolete syscall */
+ { 0, SCMP_SYS(stty) }, /* obsolete syscall */
+ { 0, SCMP_SYS(swapoff) },
+ { 0, SCMP_SYS(swapon) },
+ { 0, SCMP_SYS(sysfs) }, /* obsolete syscall */
+ { 0, SCMP_SYS(tuxcall) }, /* obsolete syscall */
+ { 0, SCMP_SYS(ulimit) }, /* obsolete syscall */
+ { 0, SCMP_SYS(uselib) }, /* obsolete syscall */
+ { 0, SCMP_SYS(ustat) }, /* obsolete syscall */
+ { 0, SCMP_SYS(vserver) }, /* obsolete syscall */
+ { CAP_SYSLOG, SCMP_SYS(syslog) },
+ { CAP_SYS_MODULE, SCMP_SYS(delete_module) },
+ { CAP_SYS_MODULE, SCMP_SYS(finit_module) },
+ { CAP_SYS_MODULE, SCMP_SYS(init_module) },
+ { CAP_SYS_PACCT, SCMP_SYS(acct) },
+ { CAP_SYS_PTRACE, SCMP_SYS(process_vm_readv) },
+ { CAP_SYS_PTRACE, SCMP_SYS(process_vm_writev) },
+ { CAP_SYS_PTRACE, SCMP_SYS(ptrace) },
+ { CAP_SYS_RAWIO, SCMP_SYS(ioperm) },
+ { CAP_SYS_RAWIO, SCMP_SYS(iopl) },
+ { CAP_SYS_RAWIO, SCMP_SYS(pciconfig_iobase) },
+ { CAP_SYS_RAWIO, SCMP_SYS(pciconfig_read) },
+ { CAP_SYS_RAWIO, SCMP_SYS(pciconfig_write) },
+#ifdef __NR_s390_pci_mmio_read
+ { CAP_SYS_RAWIO, SCMP_SYS(s390_pci_mmio_read) },
+#endif
+#ifdef __NR_s390_pci_mmio_write
+ { CAP_SYS_RAWIO, SCMP_SYS(s390_pci_mmio_write) },
+#endif
+ { CAP_SYS_TIME, SCMP_SYS(adjtimex) },
+ { CAP_SYS_TIME, SCMP_SYS(clock_adjtime) },
+ { CAP_SYS_TIME, SCMP_SYS(clock_settime) },
+ { CAP_SYS_TIME, SCMP_SYS(settimeofday) },
+ { CAP_SYS_TIME, SCMP_SYS(stime) },
};
for (i = 0; i < ELEMENTSOF(blacklist); i++) {
- if (cap_list_retain & (1ULL << blacklist[i].capability))
+ if (blacklist[i].capability != 0 && (cap_list_retain & (1ULL << blacklist[i].capability)))
continue;
r = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), blacklist[i].syscall_num, 0);
if (r == -EFAULT)
continue; /* unknown syscall */
- if (r < 0) {
- log_error_errno(r, "Failed to block syscall: %m");
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to block syscall: %m");
}
return 0;
diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h
index 1c47e37912..231e6d7266 100644
--- a/src/nspawn/nspawn-settings.h
+++ b/src/nspawn/nspawn-settings.h
@@ -56,7 +56,8 @@ typedef enum SettingsMask {
SETTING_CUSTOM_MOUNTS = 1 << 11,
SETTING_WORKING_DIRECTORY = 1 << 12,
SETTING_USERNS = 1 << 13,
- _SETTINGS_MASK_ALL = (1 << 14) -1
+ SETTING_NOTIFY_READY = 1 << 14,
+ _SETTINGS_MASK_ALL = (1 << 15) -1
} SettingsMask;
typedef struct Settings {
@@ -73,6 +74,7 @@ typedef struct Settings {
char *working_directory;
UserNamespaceMode userns_mode;
uid_t uid_shift, uid_range;
+ bool notify_ready;
/* [Image] */
int read_only;
diff --git a/src/nspawn/nspawn-setuid.c b/src/nspawn/nspawn-setuid.c
index ee15a47e93..b8e8e091c8 100644
--- a/src/nspawn/nspawn-setuid.c
+++ b/src/nspawn/nspawn-setuid.c
@@ -124,14 +124,12 @@ int change_uid_gid(const char *user, char **_home) {
fd = -1;
if (!fgets(line, sizeof(line), f)) {
-
if (!ferror(f)) {
log_error("Failed to resolve user %s.", user);
return -ESRCH;
}
- log_error_errno(errno, "Failed to read from getent: %m");
- return -errno;
+ return log_error_errno(errno, "Failed to read from getent: %m");
}
truncate_nl(line);
@@ -214,8 +212,7 @@ int change_uid_gid(const char *user, char **_home) {
return -ESRCH;
}
- log_error_errno(errno, "Failed to read from getent: %m");
- return -errno;
+ return log_error_errno(errno, "Failed to read from getent: %m");
}
truncate_nl(line);
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index d1c65e8b0b..fcf14bba4c 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -61,9 +61,9 @@
#include "fs-util.h"
#include "gpt.h"
#include "hostname-util.h"
+#include "id128-util.h"
#include "log.h"
#include "loopback-setup.h"
-#include "machine-id-setup.h"
#include "machine-image.h"
#include "macro.h"
#include "missing.h"
@@ -76,10 +76,10 @@
#include "nspawn-network.h"
#include "nspawn-patch-uid.h"
#include "nspawn-register.h"
+#include "nspawn-seccomp.h"
#include "nspawn-settings.h"
#include "nspawn-setuid.h"
#include "nspawn-stub-pid1.h"
-#include "nspawn-seccomp.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
@@ -101,10 +101,16 @@
#include "util.h"
/* Note that devpts's gid= parameter parses GIDs as signed values, hence we stay away from the upper half of the 32bit
- * UID range here */
+ * UID range here. We leave a bit of room at the lower end and a lot of room at the upper end, so that other subsystems
+ * may have their own allocation ranges too. */
#define UID_SHIFT_PICK_MIN ((uid_t) UINT32_C(0x00080000))
#define UID_SHIFT_PICK_MAX ((uid_t) UINT32_C(0x6FFF0000))
+/* nspawn is listening on the socket at the path in the constant nspawn_notify_socket_path
+ * nspawn_notify_socket_path is relative to the container
+ * the init process in the container pid can send messages to nspawn following the sd_notify(3) protocol */
+#define NSPAWN_NOTIFY_SOCKET_PATH "/run/systemd/nspawn/notify"
+
typedef enum ContainerStatus {
CONTAINER_TERMINATED,
CONTAINER_REBOOTED
@@ -133,6 +139,8 @@ static bool arg_ephemeral = false;
static LinkJournal arg_link_journal = LINK_AUTO;
static bool arg_link_journal_try = false;
static uint64_t arg_caps_retain =
+ (1ULL << CAP_AUDIT_CONTROL) |
+ (1ULL << CAP_AUDIT_WRITE) |
(1ULL << CAP_CHOWN) |
(1ULL << CAP_DAC_OVERRIDE) |
(1ULL << CAP_DAC_READ_SEARCH) |
@@ -142,23 +150,21 @@ static uint64_t arg_caps_retain =
(1ULL << CAP_KILL) |
(1ULL << CAP_LEASE) |
(1ULL << CAP_LINUX_IMMUTABLE) |
+ (1ULL << CAP_MKNOD) |
(1ULL << CAP_NET_BIND_SERVICE) |
(1ULL << CAP_NET_BROADCAST) |
(1ULL << CAP_NET_RAW) |
- (1ULL << CAP_SETGID) |
(1ULL << CAP_SETFCAP) |
+ (1ULL << CAP_SETGID) |
(1ULL << CAP_SETPCAP) |
(1ULL << CAP_SETUID) |
(1ULL << CAP_SYS_ADMIN) |
+ (1ULL << CAP_SYS_BOOT) |
(1ULL << CAP_SYS_CHROOT) |
(1ULL << CAP_SYS_NICE) |
(1ULL << CAP_SYS_PTRACE) |
- (1ULL << CAP_SYS_TTY_CONFIG) |
(1ULL << CAP_SYS_RESOURCE) |
- (1ULL << CAP_SYS_BOOT) |
- (1ULL << CAP_AUDIT_WRITE) |
- (1ULL << CAP_AUDIT_CONTROL) |
- (1ULL << CAP_MKNOD);
+ (1ULL << CAP_SYS_TTY_CONFIG);
static CustomMount *arg_custom_mounts = NULL;
static unsigned arg_n_custom_mounts = 0;
static char **arg_setenv = NULL;
@@ -187,6 +193,8 @@ static SettingsMask arg_settings_mask = 0;
static int arg_settings_trusted = -1;
static char **arg_parameters = NULL;
static const char *arg_container_service_name = "systemd-nspawn";
+static bool arg_notify_ready = false;
+static bool arg_use_cgns = true;
static void help(void) {
printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n"
@@ -208,10 +216,10 @@ static void help(void) {
" --uuid=UUID Set a specific machine UUID for the container\n"
" -S --slice=SLICE Place the container in the specified slice\n"
" --property=NAME=VALUE Set scope unit property\n"
- " -U --private-users=pick Run within user namespace, pick UID/GID range automatically\n"
+ " -U --private-users=pick Run within user namespace, autoselect UID/GID range\n"
" --private-users[=UIDBASE[:NUIDS]]\n"
- " Run within user namespace, user configured UID/GID range\n"
- " --private-user-chown Adjust OS tree file ownership for private UID/GID range\n"
+ " Similar, but with user configured UID/GID range\n"
+ " --private-user-chown Adjust OS tree ownership to private UID/GID range\n"
" --private-network Disable network in container\n"
" --network-interface=INTERFACE\n"
" Assign an existing network interface to the\n"
@@ -228,11 +236,10 @@ static void help(void) {
" Add an additional virtual Ethernet link between\n"
" host and container\n"
" --network-bridge=INTERFACE\n"
- " Add a virtual Ethernet connection between host\n"
- " and container and add it to an existing bridge on\n"
- " the host\n"
- " --network-zone=NAME Add a virtual Ethernet connection to the container,\n"
- " and add it to an automatically managed bridge interface\n"
+ " Add a virtual Ethernet connection to the container\n"
+ " and attach it to an existing bridge on the host\n"
+ " --network-zone=NAME Similar, but attach the new interface to an\n"
+ " an automatically managed bridge interface\n"
" -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n"
" Expose a container IP port on the host\n"
" -Z --selinux-context=SECLABEL\n"
@@ -261,16 +268,15 @@ static void help(void) {
" --overlay-ro=PATH[:PATH...]:PATH\n"
" Similar, but creates a read-only overlay mount\n"
" -E --setenv=NAME=VALUE Pass an environment variable to PID 1\n"
- " --share-system Share system namespaces with host\n"
" --register=BOOLEAN Register container as machine\n"
" --keep-unit Do not register a scope for the machine, reuse\n"
" the service unit nspawn is running in\n"
" --volatile[=MODE] Run the system in volatile mode\n"
" --settings=BOOLEAN Load additional settings from .nspawn file\n"
+ " --notify-ready=BOOLEAN Receive notifications from the child init process\n"
, program_invocation_short_name);
}
-
static int custom_mounts_prepare(void) {
unsigned i;
int r;
@@ -367,6 +373,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SETTINGS,
ARG_CHDIR,
ARG_PRIVATE_USERS_CHOWN,
+ ARG_NOTIFY_READY,
};
static const struct option options[] = {
@@ -395,7 +402,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "selinux-context", required_argument, NULL, 'Z' },
{ "selinux-apifs-context", required_argument, NULL, 'L' },
{ "quiet", no_argument, NULL, 'q' },
- { "share-system", no_argument, NULL, ARG_SHARE_SYSTEM },
+ { "share-system", no_argument, NULL, ARG_SHARE_SYSTEM }, /* not documented */
{ "register", required_argument, NULL, ARG_REGISTER },
{ "keep-unit", no_argument, NULL, ARG_KEEP_UNIT },
{ "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE },
@@ -415,6 +422,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL },
{ "settings", required_argument, NULL, ARG_SETTINGS },
{ "chdir", required_argument, NULL, ARG_CHDIR },
+ { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY },
{}
};
@@ -584,9 +592,12 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_UUID:
r = sd_id128_from_string(optarg, &arg_uuid);
- if (r < 0) {
- log_error("Invalid UUID: %s", optarg);
- return r;
+ if (r < 0)
+ return log_error_errno(r, "Invalid UUID: %s", optarg);
+
+ if (sd_id128_is_null(arg_uuid)) {
+ log_error("Machine UUID may not be all zeroes.");
+ return -EINVAL;
}
arg_settings_mask |= SETTING_MACHINE_ID;
@@ -800,6 +811,8 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_SHARE_SYSTEM:
+ /* We don't officially support this anymore, except for compat reasons. People should use the
+ * $SYSTEMD_NSPAWN_SHARE_SYSTEM environment variable instead. */
arg_share_system = true;
break;
@@ -987,6 +1000,16 @@ static int parse_argv(int argc, char *argv[]) {
arg_settings_mask |= SETTING_WORKING_DIRECTORY;
break;
+ case ARG_NOTIFY_READY:
+ r = parse_boolean(optarg);
+ if (r < 0) {
+ log_error("%s is not a valid notify mode. Valid modes are: yes, no, and ready.", optarg);
+ return -EINVAL;
+ }
+ arg_notify_ready = r;
+ arg_settings_mask |= SETTING_NOTIFY_READY;
+ break;
+
case '?':
return -EINVAL;
@@ -994,6 +1017,9 @@ static int parse_argv(int argc, char *argv[]) {
assert_not_reached("Unhandled option");
}
+ if (getenv_bool("SYSTEMD_NSPAWN_SHARE_SYSTEM") > 0)
+ arg_share_system = true;
+
if (arg_share_system)
arg_register = false;
@@ -1001,7 +1027,7 @@ static int parse_argv(int argc, char *argv[]) {
arg_userns_chown = true;
if (arg_start_mode != START_PID1 && arg_share_system) {
- log_error("--boot and --share-system may not be combined.");
+ log_error("--boot and SYSTEMD_NSPAWN_SHARE_SYSTEM=1 may not be combined.");
return -EINVAL;
}
@@ -1081,6 +1107,12 @@ static int parse_argv(int argc, char *argv[]) {
if (e)
arg_container_service_name = e;
+ r = getenv_bool("SYSTEMD_NSPAWN_USE_CGNS");
+ if (r < 0)
+ arg_use_cgns = cg_ns_supported();
+ else
+ arg_use_cgns = r;
+
return 1;
}
@@ -1224,42 +1256,46 @@ static int setup_resolv_conf(const char *dest) {
/* Fix resolv.conf, if possible */
where = prefix_roota(dest, "/etc/resolv.conf");
+ if (access("/usr/lib/systemd/resolv.conf", F_OK) >= 0) {
+ /* resolved is enabled on the host. In this, case bind mount its static resolv.conf file into the
+ * container, so that the container can use the host's resolver. Given that network namespacing is
+ * disabled it's only natural of the container also uses the host's resolver. It also has the big
+ * advantage that the container will be able to follow the host's DNS server configuration changes
+ * transparently. */
+
+ if (mount("/usr/lib/systemd/resolv.conf", where, NULL, MS_BIND, NULL) < 0)
+ log_warning_errno(errno, "Failed to mount /etc/resolv.conf in the container, ignoring: %m");
+ else {
+ if (mount(NULL, where, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL) < 0)
+ return log_error_errno(errno, "Failed to remount /etc/resolv.conf read-only: %m");
+
+ return 0;
+ }
+ }
+
+ /* If that didn't work, let's copy the file */
r = copy_file("/etc/resolv.conf", where, O_TRUNC|O_NOFOLLOW, 0644, 0);
if (r < 0) {
- /* If the file already exists as symlink, let's
- * suppress the warning, under the assumption that
- * resolved or something similar runs inside and the
- * symlink points there.
+ /* If the file already exists as symlink, let's suppress the warning, under the assumption that
+ * resolved or something similar runs inside and the symlink points there.
*
- * If the disk image is read-only, there's also no
- * point in complaining.
+ * If the disk image is read-only, there's also no point in complaining.
*/
log_full_errno(IN_SET(r, -ELOOP, -EROFS) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to copy /etc/resolv.conf to %s: %m", where);
+ "Failed to copy /etc/resolv.conf to %s, ignoring: %m", where);
return 0;
}
r = userns_lchown(where, 0, 0);
if (r < 0)
- log_warning_errno(r, "Failed to chown /etc/resolv.conf: %m");
+ log_warning_errno(r, "Failed to chown /etc/resolv.conf, ignoring: %m");
return 0;
}
-static char* id128_format_as_uuid(sd_id128_t id, char s[37]) {
- assert(s);
-
- snprintf(s, 37,
- "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
- SD_ID128_FORMAT_VAL(id));
-
- return s;
-}
-
static int setup_boot_id(const char *dest) {
+ sd_id128_t rnd = SD_ID128_NULL;
const char *from, *to;
- sd_id128_t rnd = {};
- char as_uuid[37];
int r;
if (arg_share_system)
@@ -1275,18 +1311,16 @@ static int setup_boot_id(const char *dest) {
if (r < 0)
return log_error_errno(r, "Failed to generate random boot id: %m");
- id128_format_as_uuid(rnd, as_uuid);
-
- r = write_string_file(from, as_uuid, WRITE_STRING_FILE_CREATE);
+ r = id128_write(from, ID128_UUID, rnd, false);
if (r < 0)
return log_error_errno(r, "Failed to write boot id: %m");
if (mount(from, to, NULL, MS_BIND, NULL) < 0)
r = log_error_errno(errno, "Failed to bind mount boot id: %m");
else if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL) < 0)
- log_warning_errno(errno, "Failed to make boot id read-only: %m");
+ r = log_error_errno(errno, "Failed to make boot id read-only: %m");
- unlink(from);
+ (void) unlink(from);
return r;
}
@@ -1784,17 +1818,18 @@ static int dissect_image(
char **root_device, bool *root_device_rw,
char **home_device, bool *home_device_rw,
char **srv_device, bool *srv_device_rw,
+ char **esp_device,
bool *secondary) {
#ifdef HAVE_BLKID
- int home_nr = -1, srv_nr = -1;
+ int home_nr = -1, srv_nr = -1, esp_nr = -1;
#ifdef GPT_ROOT_NATIVE
int root_nr = -1;
#endif
#ifdef GPT_ROOT_SECONDARY
int secondary_root_nr = -1;
#endif
- _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL, *generic = NULL;
+ _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL, *esp = NULL, *generic = NULL;
_cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL;
_cleanup_udev_device_unref_ struct udev_device *d = NULL;
_cleanup_blkid_free_probe_ blkid_probe b = NULL;
@@ -1812,6 +1847,7 @@ static int dissect_image(
assert(root_device);
assert(home_device);
assert(srv_device);
+ assert(esp_device);
assert(secondary);
assert(arg_image);
@@ -2025,6 +2061,16 @@ static int dissect_image(
r = free_and_strdup(&srv, node);
if (r < 0)
return log_oom();
+ } else if (sd_id128_equal(type_id, GPT_ESP)) {
+
+ if (esp && nr >= esp_nr)
+ continue;
+
+ esp_nr = nr;
+
+ r = free_and_strdup(&esp, node);
+ if (r < 0)
+ return log_oom();
}
#ifdef GPT_ROOT_NATIVE
else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) {
@@ -2142,6 +2188,11 @@ static int dissect_image(
*srv_device_rw = srv_rw;
}
+ if (esp) {
+ *esp_device = esp;
+ esp = NULL;
+ }
+
return 0;
#else
log_error("--image= is not supported, compiled without blkid support.");
@@ -2212,33 +2263,37 @@ static int mount_device(const char *what, const char *where, const char *directo
}
static int setup_machine_id(const char *directory) {
+ const char *etc_machine_id;
+ sd_id128_t id;
int r;
- const char *etc_machine_id, *t;
- _cleanup_free_ char *s = NULL;
- etc_machine_id = prefix_roota(directory, "/etc/machine-id");
+ /* If the UUID in the container is already set, then that's what counts, and we use. If it isn't set, and the
+ * caller passed --uuid=, then we'll pass it in the $container_uuid env var to PID 1 of the container. The
+ * assumption is that PID 1 will then write it to /etc/machine-id to make it persistent. If --uuid= is not
+ * passed we generate a random UUID, and pass it via $container_uuid. In effect this means that /etc/machine-id
+ * in the container and our idea of the container UUID will always be in sync (at least if PID 1 in the
+ * container behaves nicely). */
- r = read_one_line_file(etc_machine_id, &s);
- if (r < 0)
- return log_error_errno(r, "Failed to read machine ID from %s: %m", etc_machine_id);
+ etc_machine_id = prefix_roota(directory, "/etc/machine-id");
- t = strstrip(s);
+ r = id128_read(etc_machine_id, ID128_PLAIN, &id);
+ if (r < 0) {
+ if (!IN_SET(r, -ENOENT, -ENOMEDIUM)) /* If the file is missing or empty, we don't mind */
+ return log_error_errno(r, "Failed to read machine ID from container image: %m");
- if (!isempty(t)) {
- r = sd_id128_from_string(t, &arg_uuid);
- if (r < 0)
- return log_error_errno(r, "Failed to parse machine ID from %s: %m", etc_machine_id);
- } else {
if (sd_id128_is_null(arg_uuid)) {
r = sd_id128_randomize(&arg_uuid);
if (r < 0)
- return log_error_errno(r, "Failed to generate random machine ID: %m");
+ return log_error_errno(r, "Failed to acquire randomized machine UUID: %m");
+ }
+ } else {
+ if (sd_id128_is_null(id)) {
+ log_error("Machine ID in container image is zero, refusing.");
+ return -EINVAL;
}
- }
- r = machine_id_setup(directory, arg_uuid);
- if (r < 0)
- return log_error_errno(r, "Failed to setup machine ID: %m");
+ arg_uuid = id;
+ }
return 0;
}
@@ -2270,7 +2325,8 @@ static int mount_devices(
const char *where,
const char *root_device, bool root_device_rw,
const char *home_device, bool home_device_rw,
- const char *srv_device, bool srv_device_rw) {
+ const char *srv_device, bool srv_device_rw,
+ const char *esp_device) {
int r;
assert(where);
@@ -2293,6 +2349,27 @@ static int mount_devices(
return log_error_errno(r, "Failed to mount server data directory: %m");
}
+ if (esp_device) {
+ const char *mp, *x;
+
+ /* Mount the ESP to /efi if it exists and is empty. If it doesn't exist, use /boot instead. */
+
+ mp = "/efi";
+ x = strjoina(arg_directory, mp);
+ r = dir_is_empty(x);
+ if (r == -ENOENT) {
+ mp = "/boot";
+ x = strjoina(arg_directory, mp);
+ r = dir_is_empty(x);
+ }
+
+ if (r > 0) {
+ r = mount_device(esp_device, arg_directory, mp, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to mount ESP: %m");
+ }
+ }
+
return 0;
}
@@ -2529,6 +2606,7 @@ static int inner_child(
NULL, /* container_uuid */
NULL, /* LISTEN_FDS */
NULL, /* LISTEN_PID */
+ NULL, /* NOTIFY_SOCKET */
NULL
};
@@ -2574,9 +2652,25 @@ static int inner_child(
return -ESRCH;
}
- r = mount_systemd_cgroup_writable("", arg_unified_cgroup_hierarchy);
- if (r < 0)
- return r;
+ if (arg_use_cgns && cg_ns_supported()) {
+ r = unshare(CLONE_NEWCGROUP);
+ if (r < 0)
+ return log_error_errno(errno, "Failed to unshare cgroup namespace");
+ r = mount_cgroups(
+ "",
+ arg_unified_cgroup_hierarchy,
+ arg_userns_mode != USER_NAMESPACE_NO,
+ arg_uid_shift,
+ arg_uid_range,
+ arg_selinux_apifs_context,
+ arg_use_cgns);
+ if (r < 0)
+ return r;
+ } else {
+ r = mount_systemd_cgroup_writable("", arg_unified_cgroup_hierarchy);
+ if (r < 0)
+ return r;
+ }
r = reset_uid_gid();
if (r < 0)
@@ -2622,7 +2716,7 @@ static int inner_child(
#ifdef HAVE_SELINUX
if (arg_selinux_context)
- if (setexeccon((security_context_t) arg_selinux_context) < 0)
+ if (setexeccon(arg_selinux_context) < 0)
return log_error_errno(errno, "setexeccon(\"%s\") failed: %m", arg_selinux_context);
#endif
@@ -2642,9 +2736,9 @@ static int inner_child(
(asprintf((char**)(envp + n_env++), "LOGNAME=%s", arg_user ? arg_user : "root") < 0))
return log_oom();
- assert(!sd_id128_equal(arg_uuid, SD_ID128_NULL));
+ assert(!sd_id128_is_null(arg_uuid));
- if (asprintf((char**)(envp + n_env++), "container_uuid=%s", id128_format_as_uuid(arg_uuid, as_uuid)) < 0)
+ if (asprintf((char**)(envp + n_env++), "container_uuid=%s", id128_to_uuid_string(arg_uuid, as_uuid)) < 0)
return log_oom();
if (fdset_size(fds) > 0) {
@@ -2656,6 +2750,8 @@ static int inner_child(
(asprintf((char **)(envp + n_env++), "LISTEN_PID=1") < 0))
return log_oom();
}
+ if (asprintf((char **)(envp + n_env++), "NOTIFY_SOCKET=%s", NSPAWN_NOTIFY_SOCKET_PATH) < 0)
+ return log_oom();
env_use = strv_env_merge(2, envp, arg_setenv);
if (!env_use)
@@ -2725,6 +2821,37 @@ static int inner_child(
return log_error_errno(r, "execv() failed: %m");
}
+static int setup_sd_notify_child(void) {
+ static const int one = 1;
+ int fd = -1;
+ union sockaddr_union sa = {
+ .sa.sa_family = AF_UNIX,
+ };
+ int r;
+
+ fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to allocate notification socket: %m");
+
+ (void) mkdir_parents(NSPAWN_NOTIFY_SOCKET_PATH, 0755);
+ (void) unlink(NSPAWN_NOTIFY_SOCKET_PATH);
+
+ strncpy(sa.un.sun_path, NSPAWN_NOTIFY_SOCKET_PATH, sizeof(sa.un.sun_path)-1);
+ r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
+ if (r < 0) {
+ safe_close(fd);
+ return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path);
+ }
+
+ r = setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+ if (r < 0) {
+ safe_close(fd);
+ return log_error_errno(errno, "SO_PASSCRED failed: %m");
+ }
+
+ return fd;
+}
+
static int outer_child(
Barrier *barrier,
const char *directory,
@@ -2732,10 +2859,12 @@ static int outer_child(
const char *root_device, bool root_device_rw,
const char *home_device, bool home_device_rw,
const char *srv_device, bool srv_device_rw,
+ const char *esp_device,
bool interactive,
bool secondary,
int pid_socket,
int uuid_socket,
+ int notify_socket,
int kmsg_socket,
int rtnl_socket,
int uid_shift_socket,
@@ -2744,12 +2873,14 @@ static int outer_child(
pid_t pid;
ssize_t l;
int r;
+ _cleanup_close_ int fd = -1;
assert(barrier);
assert(directory);
assert(console);
assert(pid_socket >= 0);
assert(uuid_socket >= 0);
+ assert(notify_socket >= 0);
assert(kmsg_socket >= 0);
cg_unified_flush();
@@ -2790,7 +2921,8 @@ static int outer_child(
r = mount_devices(directory,
root_device, root_device_rw,
home_device, home_device_rw,
- srv_device, srv_device_rw);
+ srv_device, srv_device_rw,
+ esp_device);
if (r < 0)
return r;
@@ -2817,7 +2949,7 @@ static int outer_child(
if (l < 0)
return log_error_errno(errno, "Failed to recv UID shift: %m");
if (l != sizeof(arg_uid_shift)) {
- log_error("Short read while recieving UID shift.");
+ log_error("Short read while receiving UID shift.");
return -EIO;
}
}
@@ -2922,20 +3054,27 @@ static int outer_child(
if (r < 0)
return r;
- r = mount_cgroups(
- directory,
- arg_unified_cgroup_hierarchy,
- arg_userns_mode != USER_NAMESPACE_NO,
- arg_uid_shift,
- arg_uid_range,
- arg_selinux_apifs_context);
- if (r < 0)
- return r;
+ if (!arg_use_cgns || !cg_ns_supported()) {
+ r = mount_cgroups(
+ directory,
+ arg_unified_cgroup_hierarchy,
+ arg_userns_mode != USER_NAMESPACE_NO,
+ arg_uid_shift,
+ arg_uid_range,
+ arg_selinux_apifs_context,
+ arg_use_cgns);
+ if (r < 0)
+ return r;
+ }
r = mount_move_root(directory);
if (r < 0)
return log_error_errno(r, "Failed to move root directory: %m");
+ fd = setup_sd_notify_child();
+ if (fd < 0)
+ return fd;
+
pid = raw_clone(SIGCHLD|CLONE_NEWNS|
(arg_share_system ? 0 : CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS) |
(arg_private_network ? CLONE_NEWNET : 0) |
@@ -2945,6 +3084,7 @@ static int outer_child(
if (pid == 0) {
pid_socket = safe_close(pid_socket);
uuid_socket = safe_close(uuid_socket);
+ notify_socket = safe_close(notify_socket);
uid_shift_socket = safe_close(uid_shift_socket);
/* The inner child has all namespaces that are
@@ -2974,8 +3114,13 @@ static int outer_child(
return -EIO;
}
+ l = send_one_fd(notify_socket, fd, 0);
+ if (l < 0)
+ return log_error_errno(errno, "Failed to send notify fd: %m");
+
pid_socket = safe_close(pid_socket);
uuid_socket = safe_close(uuid_socket);
+ notify_socket = safe_close(notify_socket);
kmsg_socket = safe_close(kmsg_socket);
rtnl_socket = safe_close(rtnl_socket);
@@ -3058,6 +3203,95 @@ static int setup_uid_map(pid_t pid) {
return 0;
}
+static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
+ char buf[NOTIFY_BUFFER_MAX+1];
+ char *p = NULL;
+ struct iovec iovec = {
+ .iov_base = buf,
+ .iov_len = sizeof(buf)-1,
+ };
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) +
+ CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)];
+ } control = {};
+ struct msghdr msghdr = {
+ .msg_iov = &iovec,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ struct ucred *ucred = NULL;
+ ssize_t n;
+ pid_t inner_child_pid;
+ _cleanup_strv_free_ char **tags = NULL;
+
+ assert(userdata);
+
+ inner_child_pid = PTR_TO_PID(userdata);
+
+ if (revents != EPOLLIN) {
+ log_warning("Got unexpected poll event for notify fd.");
+ return 0;
+ }
+
+ n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+
+ return log_warning_errno(errno, "Couldn't read notification socket: %m");
+ }
+ cmsg_close_all(&msghdr);
+
+ CMSG_FOREACH(cmsg, &msghdr) {
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_CREDENTIALS &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
+
+ ucred = (struct ucred*) CMSG_DATA(cmsg);
+ }
+ }
+
+ if (!ucred || ucred->pid != inner_child_pid) {
+ log_warning("Received notify message without valid credentials. Ignoring.");
+ return 0;
+ }
+
+ if ((size_t) n >= sizeof(buf)) {
+ log_warning("Received notify message exceeded maximum size. Ignoring.");
+ return 0;
+ }
+
+ buf[n] = 0;
+ tags = strv_split(buf, "\n\r");
+ if (!tags)
+ return log_oom();
+
+ if (strv_find(tags, "READY=1"))
+ sd_notifyf(false, "READY=1\n");
+
+ p = strv_find_startswith(tags, "STATUS=");
+ if (p)
+ sd_notifyf(false, "STATUS=Container running: %s", p);
+
+ return 0;
+}
+
+static int setup_sd_notify_parent(sd_event *event, int fd, pid_t *inner_child_pid) {
+ int r;
+ sd_event_source *notify_event_source;
+
+ r = sd_event_add_io(event, &notify_event_source, fd, EPOLLIN, nspawn_dispatch_notify_fd, inner_child_pid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate notify event source: %m");
+
+ (void) sd_event_source_set_description(notify_event_source, "nspawn-notify");
+
+ return 0;
+}
+
static int load_settings(void) {
_cleanup_(settings_freep) Settings *settings = NULL;
_cleanup_fclose_ FILE *f = NULL;
@@ -3286,12 +3520,15 @@ static int load_settings(void) {
}
}
+ if ((arg_settings_mask & SETTING_NOTIFY_READY) == 0)
+ arg_notify_ready = settings->notify_ready;
+
return 0;
}
int main(int argc, char *argv[]) {
- _cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL, *console = NULL;
+ _cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL, *esp_device = NULL, *console = NULL;
bool root_device_rw = true, home_device_rw = true, srv_device_rw = true;
_cleanup_close_ int master = -1, image_fd = -1;
_cleanup_fdset_free_ FDSet *fds = NULL;
@@ -3400,7 +3637,7 @@ int main(int argc, char *argv[]) {
}
if (r < 0) {
log_error_errno(r, "Failed to lock %s: %m", arg_directory);
- return r;
+ goto finish;
}
if (arg_template) {
@@ -3473,6 +3710,7 @@ int main(int argc, char *argv[]) {
&root_device, &root_device_rw,
&home_device, &home_device_rw,
&srv_device, &srv_device_rw,
+ &esp_device,
&secondary);
if (r < 0)
goto finish;
@@ -3536,7 +3774,9 @@ int main(int argc, char *argv[]) {
rtnl_socket_pair[2] = { -1, -1 },
pid_socket_pair[2] = { -1, -1 },
uuid_socket_pair[2] = { -1, -1 },
+ notify_socket_pair[2] = { -1, -1 },
uid_shift_socket_pair[2] = { -1, -1 };
+ _cleanup_close_ int notify_socket= -1;
_cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_(pty_forward_freep) PTYForward *forward = NULL;
@@ -3587,6 +3827,11 @@ int main(int argc, char *argv[]) {
goto finish;
}
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, notify_socket_pair) < 0) {
+ r = log_error_errno(errno, "Failed to create notify socket pair: %m");
+ goto finish;
+ }
+
if (arg_userns_mode != USER_NAMESPACE_NO)
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uid_shift_socket_pair) < 0) {
r = log_error_errno(errno, "Failed to create uid shift socket pair: %m");
@@ -3628,6 +3873,7 @@ int main(int argc, char *argv[]) {
rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]);
pid_socket_pair[0] = safe_close(pid_socket_pair[0]);
uuid_socket_pair[0] = safe_close(uuid_socket_pair[0]);
+ notify_socket_pair[0] = safe_close(notify_socket_pair[0]);
uid_shift_socket_pair[0] = safe_close(uid_shift_socket_pair[0]);
(void) reset_all_signal_handlers();
@@ -3639,10 +3885,12 @@ int main(int argc, char *argv[]) {
root_device, root_device_rw,
home_device, home_device_rw,
srv_device, srv_device_rw,
+ esp_device,
interactive,
secondary,
pid_socket_pair[1],
uuid_socket_pair[1],
+ notify_socket_pair[1],
kmsg_socket_pair[1],
rtnl_socket_pair[1],
uid_shift_socket_pair[1],
@@ -3661,6 +3909,7 @@ int main(int argc, char *argv[]) {
rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]);
pid_socket_pair[1] = safe_close(pid_socket_pair[1]);
uuid_socket_pair[1] = safe_close(uuid_socket_pair[1]);
+ notify_socket_pair[1] = safe_close(notify_socket_pair[1]);
uid_shift_socket_pair[1] = safe_close(uid_shift_socket_pair[1]);
if (arg_userns_mode != USER_NAMESPACE_NO) {
@@ -3734,6 +3983,13 @@ int main(int argc, char *argv[]) {
goto finish;
}
+ /* We also retrieve the socket used for notifications generated by outer child */
+ notify_socket = receive_one_fd(notify_socket_pair[0], 0);
+ if (notify_socket < 0) {
+ r = log_error_errno(errno, "Failed to receive notification socket from the outer child: %m");
+ goto finish;
+ }
+
log_debug("Init process invoked as PID " PID_FMT, pid);
if (arg_userns_mode != USER_NAMESPACE_NO) {
@@ -3848,6 +4104,16 @@ int main(int argc, char *argv[]) {
goto finish;
}
+ r = sd_event_new(&event);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get default event source: %m");
+ goto finish;
+ }
+
+ r = setup_sd_notify_parent(event, notify_socket, PID_TO_PTR(pid));
+ if (r < 0)
+ goto finish;
+
/* Let the child know that we are ready and wait that the child is completely ready now. */
if (!barrier_place_and_sync(&barrier)) { /* #4 */
log_error("Child died too early.");
@@ -3860,15 +4126,10 @@ int main(int argc, char *argv[]) {
etc_passwd_lock = safe_close(etc_passwd_lock);
sd_notifyf(false,
- "READY=1\n"
"STATUS=Container running.\n"
"X_NSPAWN_LEADER_PID=" PID_FMT, pid);
-
- r = sd_event_new(&event);
- if (r < 0) {
- log_error_errno(r, "Failed to get default event source: %m");
- goto finish;
- }
+ if (!arg_notify_ready)
+ sd_notify(false, "READY=1\n");
if (arg_kill_signal > 0) {
/* Try to kill the init system on SIGINT or SIGTERM */
diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c
index 2b83d127b7..11c27575c0 100644
--- a/src/nss-myhostname/nss-myhostname.c
+++ b/src/nss-myhostname/nss-myhostname.c
@@ -38,7 +38,7 @@
* IPv6 we use ::1 which unfortunately will not translate back to the
* hostname but instead something like "localhost" or so. */
-#define LOCALADDRESS_IPV4 (htonl(0x7F000002))
+#define LOCALADDRESS_IPV4 (htobe32(0x7F000002))
#define LOCALADDRESS_IPV6 &in6addr_loopback
NSS_GETHOSTBYNAME_PROTOTYPES(myhostname);
@@ -75,7 +75,7 @@ enum nss_status _nss_myhostname_gethostbyname4_r(
* is optional */
canonical = "localhost";
- local_address_ipv4 = htonl(INADDR_LOOPBACK);
+ local_address_ipv4 = htobe32(INADDR_LOOPBACK);
} else if (is_gateway_hostname(name)) {
@@ -96,7 +96,7 @@ enum nss_status _nss_myhostname_gethostbyname4_r(
return NSS_STATUS_TRYAGAIN;
}
- /* We respond to our local host name, our our hostname suffixed with a single dot. */
+ /* We respond to our local host name, our hostname suffixed with a single dot. */
if (!streq(name, hn) && !streq_ptr(startswith(name, hn), ".")) {
*errnop = ENOENT;
*h_errnop = HOST_NOT_FOUND;
@@ -348,7 +348,7 @@ enum nss_status _nss_myhostname_gethostbyname3_r(
if (is_localhost(name)) {
canonical = "localhost";
- local_address_ipv4 = htonl(INADDR_LOOPBACK);
+ local_address_ipv4 = htobe32(INADDR_LOOPBACK);
} else if (is_gateway_hostname(name)) {
@@ -437,9 +437,9 @@ enum nss_status _nss_myhostname_gethostbyaddr2_r(
if ((*(uint32_t*) addr) == LOCALADDRESS_IPV4)
goto found;
- if ((*(uint32_t*) addr) == htonl(INADDR_LOOPBACK)) {
+ if ((*(uint32_t*) addr) == htobe32(INADDR_LOOPBACK)) {
canonical = "localhost";
- local_address_ipv4 = htonl(INADDR_LOOPBACK);
+ local_address_ipv4 = htobe32(INADDR_LOOPBACK);
goto found;
}
diff --git a/src/nss-systemd/Makefile b/src/nss-systemd/Makefile
new file mode 120000
index 0000000000..d0b0e8e008
--- /dev/null
+++ b/src/nss-systemd/Makefile
@@ -0,0 +1 @@
+../Makefile \ No newline at end of file
diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c
new file mode 100644
index 0000000000..e7a4393bb0
--- /dev/null
+++ b/src/nss-systemd/nss-systemd.c
@@ -0,0 +1,332 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <nss.h>
+
+#include "sd-bus.h"
+
+#include "bus-common-errors.h"
+#include "env-util.h"
+#include "macro.h"
+#include "nss-util.h"
+#include "signal-util.h"
+#include "user-util.h"
+#include "util.h"
+
+NSS_GETPW_PROTOTYPES(systemd);
+NSS_GETGR_PROTOTYPES(systemd);
+
+enum nss_status _nss_systemd_getpwnam_r(
+ const char *name,
+ struct passwd *pwd,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ uint32_t translated;
+ size_t l;
+ int r;
+
+ BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
+
+ assert(name);
+ assert(pwd);
+
+ /* Make sure that we don't go in circles when allocating a dynamic UID by checking our own database */
+ if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
+ goto not_found;
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_call_method(bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "LookupDynamicUserByName",
+ &error,
+ &reply,
+ "s",
+ name);
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER))
+ goto not_found;
+
+ goto fail;
+ }
+
+ r = sd_bus_message_read(reply, "u", &translated);
+ if (r < 0)
+ goto fail;
+
+ l = strlen(name);
+ if (buflen < l+1) {
+ *errnop = ENOMEM;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ memcpy(buffer, name, l+1);
+
+ pwd->pw_name = buffer;
+ pwd->pw_uid = (uid_t) translated;
+ pwd->pw_gid = (uid_t) translated;
+ pwd->pw_gecos = (char*) "Dynamic User";
+ pwd->pw_passwd = (char*) "*"; /* locked */
+ pwd->pw_dir = (char*) "/";
+ pwd->pw_shell = (char*) "/sbin/nologin";
+
+ *errnop = 0;
+ return NSS_STATUS_SUCCESS;
+
+not_found:
+ *errnop = 0;
+ return NSS_STATUS_NOTFOUND;
+
+fail:
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+}
+
+enum nss_status _nss_systemd_getpwuid_r(
+ uid_t uid,
+ struct passwd *pwd,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ const char *translated;
+ size_t l;
+ int r;
+
+ BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
+
+ if (!uid_is_valid(uid)) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ if (uid <= SYSTEM_UID_MAX)
+ goto not_found;
+
+ if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
+ goto not_found;
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_call_method(bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "LookupDynamicUserByUID",
+ &error,
+ &reply,
+ "u",
+ (uint32_t) uid);
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER))
+ goto not_found;
+
+ goto fail;
+ }
+
+ r = sd_bus_message_read(reply, "s", &translated);
+ if (r < 0)
+ goto fail;
+
+ l = strlen(translated) + 1;
+ if (buflen < l) {
+ *errnop = ENOMEM;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ memcpy(buffer, translated, l);
+
+ pwd->pw_name = buffer;
+ pwd->pw_uid = uid;
+ pwd->pw_gid = uid;
+ pwd->pw_gecos = (char*) "Dynamic User";
+ pwd->pw_passwd = (char*) "*"; /* locked */
+ pwd->pw_dir = (char*) "/";
+ pwd->pw_shell = (char*) "/sbin/nologin";
+
+ *errnop = 0;
+ return NSS_STATUS_SUCCESS;
+
+not_found:
+ *errnop = 0;
+ return NSS_STATUS_NOTFOUND;
+
+fail:
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+}
+
+enum nss_status _nss_systemd_getgrnam_r(
+ const char *name,
+ struct group *gr,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ uint32_t translated;
+ size_t l;
+ int r;
+
+ BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
+
+ assert(name);
+ assert(gr);
+
+ if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
+ goto not_found;
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_call_method(bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "LookupDynamicUserByName",
+ &error,
+ &reply,
+ "s",
+ name);
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER))
+ goto not_found;
+
+ goto fail;
+ }
+
+ r = sd_bus_message_read(reply, "u", &translated);
+ if (r < 0)
+ goto fail;
+
+ l = sizeof(char*) + strlen(name) + 1;
+ if (buflen < l) {
+ *errnop = ENOMEM;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ memzero(buffer, sizeof(char*));
+ strcpy(buffer + sizeof(char*), name);
+
+ gr->gr_name = buffer + sizeof(char*);
+ gr->gr_gid = (gid_t) translated;
+ gr->gr_passwd = (char*) "*"; /* locked */
+ gr->gr_mem = (char**) buffer;
+
+ *errnop = 0;
+ return NSS_STATUS_SUCCESS;
+
+not_found:
+ *errnop = 0;
+ return NSS_STATUS_NOTFOUND;
+
+fail:
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+}
+
+enum nss_status _nss_systemd_getgrgid_r(
+ gid_t gid,
+ struct group *gr,
+ char *buffer, size_t buflen,
+ int *errnop) {
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ const char *translated;
+ size_t l;
+ int r;
+
+ BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
+
+ if (!gid_is_valid(gid)) {
+ r = -EINVAL;
+ goto fail;
+ }
+
+ if (gid <= SYSTEM_GID_MAX)
+ goto not_found;
+
+ if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
+ goto not_found;
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_call_method(bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "LookupDynamicUserByUID",
+ &error,
+ &reply,
+ "u",
+ (uint32_t) gid);
+ if (r < 0) {
+ if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER))
+ goto not_found;
+
+ goto fail;
+ }
+
+ r = sd_bus_message_read(reply, "s", &translated);
+ if (r < 0)
+ goto fail;
+
+ l = sizeof(char*) + strlen(translated) + 1;
+ if (buflen < l) {
+ *errnop = ENOMEM;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ memzero(buffer, sizeof(char*));
+ strcpy(buffer + sizeof(char*), translated);
+
+ gr->gr_name = buffer + sizeof(char*);
+ gr->gr_gid = gid;
+ gr->gr_passwd = (char*) "*"; /* locked */
+ gr->gr_mem = (char**) buffer;
+
+ *errnop = 0;
+ return NSS_STATUS_SUCCESS;
+
+not_found:
+ *errnop = 0;
+ return NSS_STATUS_NOTFOUND;
+
+fail:
+ *errnop = -r;
+ return NSS_STATUS_UNAVAIL;
+}
diff --git a/src/nss-systemd/nss-systemd.sym b/src/nss-systemd/nss-systemd.sym
new file mode 100644
index 0000000000..955078788a
--- /dev/null
+++ b/src/nss-systemd/nss-systemd.sym
@@ -0,0 +1,17 @@
+/***
+ This file is part of systemd.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+***/
+
+{
+global:
+ _nss_systemd_getpwnam_r;
+ _nss_systemd_getpwuid_r;
+ _nss_systemd_getgrnam_r;
+ _nss_systemd_getgrgid_r;
+local: *;
+};
diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c
index 78d9d5733f..aaf5ed62c1 100644
--- a/src/resolve/dns-type.c
+++ b/src/resolve/dns-type.c
@@ -96,6 +96,15 @@ bool dns_type_is_valid_query(uint16_t type) {
DNS_TYPE_RRSIG);
}
+bool dns_type_is_zone_transer(uint16_t type) {
+
+ /* Zone transfers, either normal or incremental */
+
+ return IN_SET(type,
+ DNS_TYPE_AXFR,
+ DNS_TYPE_IXFR);
+}
+
bool dns_type_is_valid_rr(uint16_t type) {
/* The types valid as RR in packets (but not necessarily
diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h
index 7b79d29d7e..e675fe4ea3 100644
--- a/src/resolve/dns-type.h
+++ b/src/resolve/dns-type.h
@@ -136,6 +136,7 @@ bool dns_type_is_obsolete(uint16_t type);
bool dns_type_may_wildcard(uint16_t type);
bool dns_type_apex_only(uint16_t type);
bool dns_type_needs_authentication(uint16_t type);
+bool dns_type_is_zone_transer(uint16_t type);
int dns_type_to_af(uint16_t type);
bool dns_class_is_pseudo(uint16_t class);
diff --git a/src/resolve/resolv.conf b/src/resolve/resolv.conf
new file mode 100644
index 0000000000..b8034d6829
--- /dev/null
+++ b/src/resolve/resolv.conf
@@ -0,0 +1,11 @@
+# This is a static resolv.conf file for connecting local clients to
+# systemd-resolved via its DNS stub listener on 127.0.0.53.
+#
+# Third party programs must not access this file directly, but only through the
+# symlink at /etc/resolv.conf. To manage resolv.conf(5) in a different way,
+# replace this symlink by a static file or a different symlink.
+#
+# See systemd-resolved.service(8) for details about the supported modes of
+# operation for /etc/resolv.conf.
+
+nameserver 127.0.0.53
diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c
index 7e145c64c4..07e4cd7d1d 100644
--- a/src/resolve/resolve-tool.c
+++ b/src/resolve/resolve-tool.c
@@ -21,17 +21,21 @@
#include <net/if.h>
#include "sd-bus.h"
+#include "sd-netlink.h"
#include "af-list.h"
#include "alloc-util.h"
#include "bus-error.h"
#include "bus-util.h"
#include "escape.h"
-#include "in-addr-util.h"
#include "gcrypt-util.h"
+#include "in-addr-util.h"
+#include "netlink-util.h"
+#include "pager.h"
#include "parse-util.h"
#include "resolved-def.h"
#include "resolved-dns-packet.h"
+#include "strv.h"
#include "terminal-util.h"
#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC)
@@ -42,6 +46,7 @@ static uint16_t arg_type = 0;
static uint16_t arg_class = 0;
static bool arg_legend = true;
static uint64_t arg_flags = 0;
+static bool arg_no_pager = false;
typedef enum ServiceFamily {
SERVICE_FAMILY_TCP,
@@ -66,6 +71,8 @@ static enum {
MODE_RESOLVE_TLSA,
MODE_STATISTICS,
MODE_RESET_STATISTICS,
+ MODE_FLUSH_CACHES,
+ MODE_STATUS,
} arg_mode = MODE_RESOLVE_HOST;
static ServiceFamily service_family_from_string(const char *s) {
@@ -198,7 +205,7 @@ static int resolve_host(sd_bus *bus, const char *name) {
if (ifindex > 0 && !if_indextoname(ifindex, ifname))
log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex);
- r = in_addr_to_string(family, a, &pretty);
+ r = in_addr_ifindex_to_string(family, a, ifindex, &pretty);
if (r < 0)
return log_error_errno(r, "Failed to print address for %s: %m", name);
@@ -252,7 +259,7 @@ static int resolve_address(sd_bus *bus, int family, const union in_addr_union *a
if (ifindex <= 0)
ifindex = arg_ifindex;
- r = in_addr_to_string(family, address, &pretty);
+ r = in_addr_ifindex_to_string(family, address, ifindex, &pretty);
if (r < 0)
return log_oom();
@@ -344,31 +351,6 @@ static int resolve_address(sd_bus *bus, int family, const union in_addr_union *a
return 0;
}
-static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) {
- const char *percent, *a;
- int ifi = 0;
- int r;
-
- percent = strchr(s, '%');
- if (percent) {
- if (parse_ifindex(percent+1, &ifi) < 0) {
- ifi = if_nametoindex(percent+1);
- if (ifi <= 0)
- return -EINVAL;
- }
-
- a = strndupa(s, percent - s);
- } else
- a = s;
-
- r = in_addr_from_string_auto(a, family, address);
- if (r < 0)
- return r;
-
- *ifindex = ifi;
- return 0;
-}
-
static int output_rr_packet(const void *d, size_t l, int ifindex) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
@@ -1037,6 +1019,490 @@ static int reset_statistics(sd_bus *bus) {
return 0;
}
+static int flush_caches(sd_bus *bus) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ r = sd_bus_call_method(bus,
+ "org.freedesktop.resolve1",
+ "/org/freedesktop/resolve1",
+ "org.freedesktop.resolve1.Manager",
+ "FlushCaches",
+ &error,
+ NULL,
+ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to flush caches: %s", bus_error_message(&error, r));
+
+ return 0;
+}
+
+static int map_link_dns_servers(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+ char ***l = userdata;
+ int r;
+
+ assert(bus);
+ assert(member);
+ assert(m);
+ assert(l);
+
+ r = sd_bus_message_enter_container(m, 'a', "(iay)");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ const void *a;
+ char *pretty;
+ int family;
+ size_t sz;
+
+ r = sd_bus_message_enter_container(m, 'r', "iay");
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ r = sd_bus_message_read(m, "i", &family);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read_array(m, 'y', &a, &sz);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ if (!IN_SET(family, AF_INET, AF_INET6)) {
+ log_debug("Unexpected family, ignoring.");
+ continue;
+ }
+
+ if (sz != FAMILY_ADDRESS_SIZE(family)) {
+ log_debug("Address size mismatch, ignoring.");
+ continue;
+ }
+
+ r = in_addr_to_string(family, a, &pretty);
+ if (r < 0)
+ return r;
+
+ r = strv_consume(l, pretty);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int map_link_domains(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+ char ***l = userdata;
+ int r;
+
+ assert(bus);
+ assert(member);
+ assert(m);
+ assert(l);
+
+ r = sd_bus_message_enter_container(m, 'a', "(sb)");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ const char *domain;
+ int route_only;
+ char *pretty;
+
+ r = sd_bus_message_read(m, "(sb)", &domain, &route_only);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (route_only)
+ pretty = strappend("~", domain);
+ else
+ pretty = strdup(domain);
+ if (!pretty)
+ return -ENOMEM;
+
+ r = strv_consume(l, pretty);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int status_ifindex(sd_bus *bus, int ifindex, const char *name, bool *empty_line) {
+
+ struct link_info {
+ uint64_t scopes_mask;
+ char *llmnr;
+ char *mdns;
+ char *dnssec;
+ char **dns;
+ char **domains;
+ char **ntas;
+ int dnssec_supported;
+ } link_info = {};
+
+ static const struct bus_properties_map property_map[] = {
+ { "ScopesMask", "t", NULL, offsetof(struct link_info, scopes_mask) },
+ { "DNS", "a(iay)", map_link_dns_servers, offsetof(struct link_info, dns) },
+ { "Domains", "a(sb)", map_link_domains, offsetof(struct link_info, domains) },
+ { "LLMNR", "s", NULL, offsetof(struct link_info, llmnr) },
+ { "MulticastDNS", "s", NULL, offsetof(struct link_info, mdns) },
+ { "DNSSEC", "s", NULL, offsetof(struct link_info, dnssec) },
+ { "DNSSECNegativeTrustAnchors", "as", NULL, offsetof(struct link_info, ntas) },
+ { "DNSSECSupported", "b", NULL, offsetof(struct link_info, dnssec_supported) },
+ {}
+ };
+
+ _cleanup_free_ char *ifi = NULL, *p = NULL;
+ char ifname[IF_NAMESIZE] = "";
+ char **i;
+ int r;
+
+ assert(bus);
+ assert(ifindex > 0);
+ assert(empty_line);
+
+ if (!name) {
+ if (!if_indextoname(ifindex, ifname))
+ return log_error_errno(errno, "Failed to resolve interface name for %i: %m", ifindex);
+
+ name = ifname;
+ }
+
+ if (asprintf(&ifi, "%i", ifindex) < 0)
+ return log_oom();
+
+ r = sd_bus_path_encode("/org/freedesktop/resolve1/link", ifi, &p);
+ if (r < 0)
+ return log_oom();
+
+ r = bus_map_all_properties(bus,
+ "org.freedesktop.resolve1",
+ p,
+ property_map,
+ &link_info);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get link data for %i: %m", ifindex);
+ goto finish;
+ }
+
+ pager_open(arg_no_pager, false);
+
+ if (*empty_line)
+ fputc('\n', stdout);
+
+ printf("%sLink %i (%s)%s\n",
+ ansi_highlight(), ifindex, name, ansi_normal());
+
+ if (link_info.scopes_mask == 0)
+ printf(" Current Scopes: none\n");
+ else
+ printf(" Current Scopes:%s%s%s%s%s\n",
+ link_info.scopes_mask & SD_RESOLVED_DNS ? " DNS" : "",
+ link_info.scopes_mask & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "",
+ link_info.scopes_mask & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "",
+ link_info.scopes_mask & SD_RESOLVED_MDNS_IPV4 ? " mDNS/IPv4" : "",
+ link_info.scopes_mask & SD_RESOLVED_MDNS_IPV6 ? " mDNS/IPv6" : "");
+
+ printf(" LLMNR setting: %s\n"
+ "MulticastDNS setting: %s\n"
+ " DNSSEC setting: %s\n"
+ " DNSSEC supported: %s\n",
+ strna(link_info.llmnr),
+ strna(link_info.mdns),
+ strna(link_info.dnssec),
+ yes_no(link_info.dnssec_supported));
+
+ STRV_FOREACH(i, link_info.dns) {
+ printf(" %s %s\n",
+ i == link_info.dns ? "DNS Servers:" : " ",
+ *i);
+ }
+
+ STRV_FOREACH(i, link_info.domains) {
+ printf(" %s %s\n",
+ i == link_info.domains ? "DNS Domain:" : " ",
+ *i);
+ }
+
+ STRV_FOREACH(i, link_info.ntas) {
+ printf(" %s %s\n",
+ i == link_info.ntas ? "DNSSEC NTA:" : " ",
+ *i);
+ }
+
+ *empty_line = true;
+
+ r = 0;
+
+finish:
+ strv_free(link_info.dns);
+ strv_free(link_info.domains);
+ free(link_info.llmnr);
+ free(link_info.mdns);
+ free(link_info.dnssec);
+ strv_free(link_info.ntas);
+ return r;
+}
+
+static int map_global_dns_servers(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+ char ***l = userdata;
+ int r;
+
+ assert(bus);
+ assert(member);
+ assert(m);
+ assert(l);
+
+ r = sd_bus_message_enter_container(m, 'a', "(iiay)");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ const void *a;
+ char *pretty;
+ int family, ifindex;
+ size_t sz;
+
+ r = sd_bus_message_enter_container(m, 'r', "iiay");
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ r = sd_bus_message_read(m, "ii", &ifindex, &family);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read_array(m, 'y', &a, &sz);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ if (ifindex != 0) /* only show the global ones here */
+ continue;
+
+ if (!IN_SET(family, AF_INET, AF_INET6)) {
+ log_debug("Unexpected family, ignoring.");
+ continue;
+ }
+
+ if (sz != FAMILY_ADDRESS_SIZE(family)) {
+ log_debug("Address size mismatch, ignoring.");
+ continue;
+ }
+
+ r = in_addr_to_string(family, a, &pretty);
+ if (r < 0)
+ return r;
+
+ r = strv_consume(l, pretty);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int map_global_domains(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+ char ***l = userdata;
+ int r;
+
+ assert(bus);
+ assert(member);
+ assert(m);
+ assert(l);
+
+ r = sd_bus_message_enter_container(m, 'a', "(isb)");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ const char *domain;
+ int route_only, ifindex;
+ char *pretty;
+
+ r = sd_bus_message_read(m, "(isb)", &ifindex, &domain, &route_only);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (ifindex != 0) /* only show the global ones here */
+ continue;
+
+ if (route_only)
+ pretty = strappend("~", domain);
+ else
+ pretty = strdup(domain);
+ if (!pretty)
+ return -ENOMEM;
+
+ r = strv_consume(l, pretty);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int status_global(sd_bus *bus, bool *empty_line) {
+
+ struct global_info {
+ char **dns;
+ char **domains;
+ char **ntas;
+ } global_info = {};
+
+ static const struct bus_properties_map property_map[] = {
+ { "DNS", "a(iiay)", map_global_dns_servers, offsetof(struct global_info, dns) },
+ { "Domains", "a(isb)", map_global_domains, offsetof(struct global_info, domains) },
+ { "DNSSECNegativeTrustAnchors", "as", NULL, offsetof(struct global_info, ntas) },
+ {}
+ };
+
+ char **i;
+ int r;
+
+ assert(bus);
+ assert(empty_line);
+
+ r = bus_map_all_properties(bus,
+ "org.freedesktop.resolve1",
+ "/org/freedesktop/resolve1",
+ property_map,
+ &global_info);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get global data: %m");
+ goto finish;
+ }
+
+ if (strv_isempty(global_info.dns) && strv_isempty(global_info.domains) && strv_isempty(global_info.ntas)) {
+ r = 0;
+ goto finish;
+ }
+
+ pager_open(arg_no_pager, false);
+
+ printf("%sGlobal%s\n", ansi_highlight(), ansi_normal());
+ STRV_FOREACH(i, global_info.dns) {
+ printf(" %s %s\n",
+ i == global_info.dns ? "DNS Servers:" : " ",
+ *i);
+ }
+
+ STRV_FOREACH(i, global_info.domains) {
+ printf(" %s %s\n",
+ i == global_info.domains ? "DNS Domain:" : " ",
+ *i);
+ }
+
+ strv_sort(global_info.ntas);
+ STRV_FOREACH(i, global_info.ntas) {
+ printf(" %s %s\n",
+ i == global_info.ntas ? "DNSSEC NTA:" : " ",
+ *i);
+ }
+
+ *empty_line = true;
+
+ r = 0;
+
+finish:
+ strv_free(global_info.dns);
+ strv_free(global_info.domains);
+ strv_free(global_info.ntas);
+
+ return r;
+}
+
+static int status_all(sd_bus *bus) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ sd_netlink_message *i;
+ bool empty_line = false;
+ int r;
+
+ assert(bus);
+
+ r = status_global(bus, &empty_line);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_open(&rtnl);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to netlink: %m");
+
+ r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ r = sd_netlink_message_request_dump(req, true);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ r = sd_netlink_call(rtnl, req, 0, &reply);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate links: %m");
+
+ r = 0;
+ for (i = reply; i; i = sd_netlink_message_next(i)) {
+ const char *name;
+ int ifindex, q;
+ uint16_t type;
+
+ q = sd_netlink_message_get_type(i, &type);
+ if (q < 0)
+ return rtnl_log_parse_error(q);
+
+ if (type != RTM_NEWLINK)
+ continue;
+
+ q = sd_rtnl_message_link_get_ifindex(i, &ifindex);
+ if (q < 0)
+ return rtnl_log_parse_error(q);
+
+ if (ifindex == LOOPBACK_IFINDEX)
+ continue;
+
+ q = sd_netlink_message_read_string(i, IFLA_IFNAME, &name);
+ if (q < 0)
+ return rtnl_log_parse_error(q);
+
+ q = status_ifindex(bus, ifindex, name, &empty_line);
+ if (q < 0 && r >= 0)
+ r = q;
+ }
+
+ return r;
+}
+
static void help_protocol_types(void) {
if (arg_legend)
puts("Known protocol types:");
@@ -1044,8 +1510,8 @@ static void help_protocol_types(void) {
}
static void help_dns_types(void) {
- int i;
const char *t;
+ int i;
if (arg_legend)
puts("Known DNS RR types:");
@@ -1057,8 +1523,8 @@ static void help_dns_types(void) {
}
static void help_dns_classes(void) {
- int i;
const char *t;
+ int i;
if (arg_legend)
puts("Known DNS RR classes:");
@@ -1076,9 +1542,10 @@ static void help(void) {
"%1$s [OPTIONS...] --statistics\n"
"%1$s [OPTIONS...] --reset-statistics\n"
"\n"
- "Resolve domain names, IPv4 and IPv6 addresses, DNS resource records, and services.\n\n"
+ "Resolve domain names, IPv4 and IPv6 addresses, DNS records, and services.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
+ " --no-pager Do not pipe output into a pager\n"
" -4 Resolve IPv4 addresses\n"
" -6 Resolve IPv6 addresses\n"
" -i --interface=INTERFACE Look on interface\n"
@@ -1097,6 +1564,8 @@ static void help(void) {
" --legend=BOOL Print headers and additional info (default: yes)\n"
" --statistics Show resolver statistics\n"
" --reset-statistics Reset resolver statistics\n"
+ " --status Show link and server status\n"
+ " --flush-caches Flush all local DNS caches\n"
, program_invocation_short_name);
}
@@ -1114,6 +1583,9 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SEARCH,
ARG_STATISTICS,
ARG_RESET_STATISTICS,
+ ARG_STATUS,
+ ARG_FLUSH_CACHES,
+ ARG_NO_PAGER,
};
static const struct option options[] = {
@@ -1134,6 +1606,9 @@ static int parse_argv(int argc, char *argv[]) {
{ "search", required_argument, NULL, ARG_SEARCH },
{ "statistics", no_argument, NULL, ARG_STATISTICS, },
{ "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS },
+ { "status", no_argument, NULL, ARG_STATUS },
+ { "flush-caches", no_argument, NULL, ARG_FLUSH_CACHES },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
{}
};
@@ -1307,6 +1782,18 @@ static int parse_argv(int argc, char *argv[]) {
arg_mode = MODE_RESET_STATISTICS;
break;
+ case ARG_FLUSH_CACHES:
+ arg_mode = MODE_FLUSH_CACHES;
+ break;
+
+ case ARG_STATUS:
+ arg_mode = MODE_STATUS;
+ break;
+
+ case ARG_NO_PAGER:
+ arg_no_pager = true;
+ break;
+
case '?':
return -EINVAL;
@@ -1366,7 +1853,7 @@ int main(int argc, char **argv) {
if (startswith(argv[optind], "dns:"))
k = resolve_rfc4501(bus, argv[optind]);
else {
- k = parse_address(argv[optind], &family, &a, &ifindex);
+ k = in_addr_ifindex_from_string_auto(argv[optind], &family, &a, &ifindex);
if (k >= 0)
k = resolve_address(bus, family, &a, ifindex);
else
@@ -1473,8 +1960,48 @@ int main(int argc, char **argv) {
r = reset_statistics(bus);
break;
+
+ case MODE_FLUSH_CACHES:
+ if (argc > optind) {
+ log_error("Too many arguments.");
+ r = -EINVAL;
+ goto finish;
+ }
+
+ r = flush_caches(bus);
+ break;
+
+ case MODE_STATUS:
+
+ if (argc > optind) {
+ char **ifname;
+ bool empty_line = false;
+
+ r = 0;
+ STRV_FOREACH(ifname, argv + optind) {
+ int ifindex, q;
+
+ q = parse_ifindex(argv[optind], &ifindex);
+ if (q < 0) {
+ ifindex = if_nametoindex(argv[optind]);
+ if (ifindex <= 0) {
+ log_error_errno(errno, "Failed to resolve interface name: %s", argv[optind]);
+ continue;
+ }
+ }
+
+ q = status_ifindex(bus, ifindex, NULL, &empty_line);
+ if (q < 0 && r >= 0)
+ r = q;
+ }
+ } else
+ r = status_all(bus);
+
+ break;
}
finish:
+ pager_close();
+
return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c
index 33f7c61557..2ca65e6953 100644
--- a/src/resolve/resolved-bus.c
+++ b/src/resolve/resolved-bus.c
@@ -245,17 +245,22 @@ static int parse_as_address(sd_bus_message *m, int ifindex, const char *hostname
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ char *canonical = NULL;
union in_addr_union parsed;
- int r, ff;
+ int r, ff, parsed_ifindex = 0;
/* Check if the hostname is actually already an IP address formatted as string. In that case just parse it,
* let's not attempt to look it up. */
- r = in_addr_from_string_auto(hostname, &ff, &parsed);
+ r = in_addr_ifindex_from_string_auto(hostname, &ff, &parsed, &parsed_ifindex);
if (r < 0) /* not an address */
return 0;
if (family != AF_UNSPEC && ff != family)
return sd_bus_reply_method_errorf(m, BUS_ERROR_NO_SUCH_RR, "The specified address is not of the requested family.");
+ if (ifindex > 0 && parsed_ifindex > 0 && parsed_ifindex != ifindex)
+ return sd_bus_reply_method_errorf(m, BUS_ERROR_NO_SUCH_RR, "The specified address interface index does not match requested interface.");
+
+ if (parsed_ifindex > 0)
+ ifindex = parsed_ifindex;
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
@@ -288,7 +293,7 @@ static int parse_as_address(sd_bus_message *m, int ifindex, const char *hostname
/* When an IP address is specified we just return it as canonical name, in order to avoid a DNS
* look-up. However, we reformat it to make sure it's in a truly canonical form (i.e. on IPv6 the inner
* omissions are always done the same way). */
- r = in_addr_to_string(ff, &parsed, &canonical);
+ r = in_addr_ifindex_to_string(ff, &parsed, ifindex, &canonical);
if (r < 0)
return r;
@@ -642,6 +647,8 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
if (!dns_type_is_valid_query(type))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified resource record type %" PRIu16 " may not be used in a query.", type);
+ if (dns_type_is_zone_transer(type))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Zone transfers not permitted via this programming interface.");
if (dns_type_is_obsolete(type))
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type);
@@ -665,6 +672,10 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
if (r < 0)
return r;
+ /* Let's request that the TTL is fixed up for locally cached entries, after all we return it in the wire format
+ * blob */
+ q->clamp_ttl = true;
+
q->request = sd_bus_message_ref(message);
q->complete = bus_method_resolve_record_complete;
@@ -1221,7 +1232,7 @@ int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex
return r;
if (with_ifindex) {
- r = sd_bus_message_append(reply, "i", s->link ? s->link->ifindex : 0);
+ r = sd_bus_message_append(reply, "i", dns_server_ifindex(s));
if (r < 0)
return r;
}
@@ -1409,6 +1420,36 @@ static int bus_property_get_dnssec_supported(
return sd_bus_message_append(reply, "b", manager_dnssec_supported(m));
}
+static int bus_property_get_ntas(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Manager *m = userdata;
+ const char *domain;
+ Iterator i;
+ int r;
+
+ assert(reply);
+ assert(m);
+
+ r = sd_bus_message_open_container(reply, 'a', "s");
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(domain, m->trust_anchor.negative_by_name, i) {
+ r = sd_bus_message_append(reply, "s", domain);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
DnsScope *s;
@@ -1442,26 +1483,6 @@ static int get_any_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error
return 0;
}
-static int get_unmanaged_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error) {
- Link *l;
- int r;
-
- assert(m);
- assert(ret);
-
- r = get_any_link(m, ifindex, &l, error);
- if (r < 0)
- return r;
-
- if (l->flags & IFF_LOOPBACK)
- return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is loopback device.", l->name);
- if (l->is_managed)
- return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is managed.", l->name);
-
- *ret = l;
- return 0;
-}
-
static int call_link_method(Manager *m, sd_bus_message *message, sd_bus_message_handler_t handler, sd_bus_error *error) {
int ifindex, r;
Link *l;
@@ -1475,7 +1496,7 @@ static int call_link_method(Manager *m, sd_bus_message *message, sd_bus_message_
if (r < 0)
return r;
- r = get_unmanaged_link(m, ifindex, &l, error);
+ r = get_any_link(m, ifindex, &l, error);
if (r < 0)
return r;
@@ -1535,6 +1556,17 @@ static int bus_method_get_link(sd_bus_message *message, void *userdata, sd_bus_e
return sd_bus_reply_method_return(message, "o", p);
}
+static int bus_method_flush_caches(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ Manager *m = userdata;
+
+ assert(message);
+ assert(m);
+
+ manager_flush_caches(m);
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0),
@@ -1544,12 +1576,14 @@ static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_PROPERTY("CacheStatistics", "(ttt)", bus_property_get_cache_statistics, 0, 0),
SD_BUS_PROPERTY("DNSSECStatistics", "(tttt)", bus_property_get_dnssec_statistics, 0, 0),
SD_BUS_PROPERTY("DNSSECSupported", "b", bus_property_get_dnssec_supported, 0, 0),
+ SD_BUS_PROPERTY("DNSSECNegativeTrustAnchors", "as", bus_property_get_ntas, 0, 0),
SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResetStatistics", NULL, NULL, bus_method_reset_statistics, 0),
+ SD_BUS_METHOD("FlushCaches", NULL, NULL, bus_method_flush_caches, 0),
SD_BUS_METHOD("GetLink", "i", "o", bus_method_get_link, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetLinkDNS", "ia(iay)", NULL, bus_method_set_link_dns_servers, 0),
SD_BUS_METHOD("SetLinkDomains", "ia(sb)", NULL, bus_method_set_link_domains, 0),
diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c
index 990dc03b60..dd233e7c4a 100644
--- a/src/resolve/resolved-conf.c
+++ b/src/resolve/resolved-conf.c
@@ -27,18 +27,22 @@
int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) {
union in_addr_union address;
- int family, r;
+ int family, r, ifindex = 0;
DnsServer *s;
assert(m);
assert(word);
- r = in_addr_from_string_auto(word, &family, &address);
+ r = in_addr_ifindex_from_string_auto(word, &family, &address, &ifindex);
if (r < 0)
return r;
+ /* Silently filter out 0.0.0.0 and 127.0.0.53 (our own stub DNS listener) */
+ if (!dns_server_address_valid(family, &address))
+ return 0;
+
/* Filter out duplicates */
- s = dns_server_find(manager_get_first_dns_server(m, type), family, &address);
+ s = dns_server_find(manager_get_first_dns_server(m, type), family, &address, ifindex);
if (s) {
/*
* Drop the marker. This is used to find the servers
@@ -50,7 +54,7 @@ int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char
return 0;
}
- return dns_server_new(m, NULL, type, NULL, family, &address);
+ return dns_server_new(m, NULL, type, NULL, family, &address, ifindex);
}
int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) {
@@ -70,7 +74,7 @@ int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, con
r = manager_add_dns_server_by_string(m, type, word);
if (r < 0)
- log_warning_errno(r, "Failed to add DNS server address '%s', ignoring.", word);
+ log_warning_errno(r, "Failed to add DNS server address '%s', ignoring: %m", word);
}
return 0;
@@ -125,7 +129,7 @@ int manager_parse_search_domains_and_warn(Manager *m, const char *string) {
r = manager_add_search_domain_by_string(m, word);
if (r < 0)
- log_warning_errno(r, "Failed to add search domain '%s', ignoring.", word);
+ log_warning_errno(r, "Failed to add search domain '%s', ignoring: %m", word);
}
return 0;
diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c
index 0dadf8b1dd..ab85754bf7 100644
--- a/src/resolve/resolved-dns-answer.c
+++ b/src/resolve/resolved-dns-answer.c
@@ -185,7 +185,7 @@ int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, Dns
return dns_answer_add(*a, rr, ifindex, flags);
}
-int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) {
+int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *soa = NULL;
soa = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SOA, name);
@@ -208,7 +208,7 @@ int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) {
soa->soa.expire = 1;
soa->soa.minimum = ttl;
- return dns_answer_add(a, soa, 0, DNS_ANSWER_AUTHENTICATED);
+ return dns_answer_add(a, soa, ifindex, DNS_ANSWER_AUTHENTICATED);
}
int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) {
@@ -702,7 +702,7 @@ void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) {
if (a->items[i].rr->key->class == DNS_CLASS_IN &&
((a->items[i].rr->key->type == DNS_TYPE_A && in_addr_is_link_local(AF_INET, (union in_addr_union*) &a->items[i].rr->a.in_addr) != prefer_link_local) ||
(a->items[i].rr->key->type == DNS_TYPE_AAAA && in_addr_is_link_local(AF_INET6, (union in_addr_union*) &a->items[i].rr->aaaa.in6_addr) != prefer_link_local)))
- /* Order address records that are are not preferred to the end of the array */
+ /* Order address records that are not preferred to the end of the array */
items[end--] = a->items[i];
else
/* Order all other records to the beginning of the array */
diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h
index 0679c610f5..4a92bd1150 100644
--- a/src/resolve/resolved-dns-answer.h
+++ b/src/resolve/resolved-dns-answer.h
@@ -56,7 +56,7 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a);
int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags);
int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags);
-int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl);
+int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl, int ifindex);
int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags);
int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *combined_flags);
@@ -87,6 +87,10 @@ static inline unsigned dns_answer_size(DnsAnswer *a) {
return a ? a->n_rrs : 0;
}
+static inline bool dns_answer_isempty(DnsAnswer *a) {
+ return dns_answer_size(a) <= 0;
+}
+
void dns_answer_dump(DnsAnswer *answer, FILE *f);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref);
diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c
index 77c42d7aad..9233fb0ac1 100644
--- a/src/resolve/resolved-dns-cache.c
+++ b/src/resolve/resolved-dns-cache.c
@@ -624,6 +624,12 @@ int dns_cache_put(
dns_cache_remove_previous(c, key, answer);
+ /* We only care for positive replies and NXDOMAINs, on all
+ * other replies we will simply flush the respective entries,
+ * and that's it */
+ if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
+ return 0;
+
if (dns_answer_size(answer) <= 0) {
char key_str[DNS_RESOURCE_KEY_STRING_MAX];
@@ -632,12 +638,6 @@ int dns_cache_put(
return 0;
}
- /* We only care for positive replies and NXDOMAINs, on all
- * other replies we will simply flush the respective entries,
- * and that's it */
- if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN))
- return 0;
-
cache_keys = dns_answer_size(answer);
if (key)
cache_keys++;
@@ -691,7 +691,7 @@ int dns_cache_put(
return 0;
/* See https://tools.ietf.org/html/rfc2308, which say that a
- * matching SOA record in the packet is used to to enable
+ * matching SOA record in the packet is used to enable
* negative caching. */
r = dns_answer_find_soa(answer, key, &soa, &flags);
if (r < 0)
@@ -790,7 +790,7 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D
return NULL;
}
-int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) {
+int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcode, DnsAnswer **ret, bool *authenticated) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
char key_str[DNS_RESOURCE_KEY_STRING_MAX];
unsigned n = 0;
@@ -798,6 +798,7 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
bool nxdomain = false;
DnsCacheItem *j, *first, *nsec = NULL;
bool have_authenticated = false, have_non_authenticated = false;
+ usec_t current;
assert(c);
assert(key);
@@ -892,11 +893,24 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r
if (!answer)
return -ENOMEM;
+ if (clamp_ttl)
+ current = now(clock_boottime_or_monotonic());
+
LIST_FOREACH(by_key, j, first) {
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+
if (!j->rr)
continue;
- r = dns_answer_add(answer, j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0);
+ if (clamp_ttl) {
+ rr = dns_resource_record_ref(j->rr);
+
+ r = dns_resource_record_clamp_ttl(&rr, LESS_BY(j->until, current) / USEC_PER_SEC);
+ if (r < 0)
+ return r;
+ }
+
+ r = dns_answer_add(answer, rr ?: j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0);
if (r < 0)
return r;
}
diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h
index 2293718e86..22a7c17377 100644
--- a/src/resolve/resolved-dns-cache.h
+++ b/src/resolve/resolved-dns-cache.h
@@ -40,7 +40,7 @@ void dns_cache_flush(DnsCache *c);
void dns_cache_prune(DnsCache *c);
int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address);
-int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer, bool *authenticated);
+int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, bool clamp_ttl, int *rcode, DnsAnswer **answer, bool *authenticated);
int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address);
diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c
index a54aed3a63..d4a267c89f 100644
--- a/src/resolve/resolved-dns-dnssec.c
+++ b/src/resolve/resolved-dns-dnssec.c
@@ -1642,7 +1642,7 @@ static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) {
if (r <= 0)
return r;
- /* If the name we we are interested in is not a prefix of the common suffix of the NSEC RR's owner and next domain names, then we can't say anything either. */
+ /* If the name we are interested in is not a prefix of the common suffix of the NSEC RR's owner and next domain names, then we can't say anything either. */
r = dns_name_common_suffix(dns_resource_key_name(rr->key), rr->nsec.next_domain_name, &common_suffix);
if (r < 0)
return r;
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index b7907bb511..a8ad8fe342 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -264,6 +264,7 @@ int dns_packet_validate_query(DnsPacket *p) {
switch (p->protocol) {
case DNS_PROTOCOL_LLMNR:
+ case DNS_PROTOCOL_DNS:
/* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */
if (DNS_PACKET_QDCOUNT(p) != 1)
return -EBADMSG;
@@ -676,13 +677,15 @@ fail:
}
/* Append the OPT pseudo-RR described in RFC6891 */
-int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) {
+int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, int rcode, size_t *start) {
size_t saved_size;
int r;
assert(p);
/* we must never advertise supported packet size smaller than the legacy max */
assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX);
+ assert(rcode >= 0);
+ assert(rcode <= _DNS_RCODE_MAX);
if (p->opt_start != (size_t) -1)
return -EBUSY;
@@ -701,13 +704,13 @@ int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, si
if (r < 0)
goto fail;
- /* maximum udp packet that can be received */
+ /* class: maximum udp packet that can be received */
r = dns_packet_append_uint16(p, max_udp_size, NULL);
if (r < 0)
goto fail;
/* extended RCODE and VERSION */
- r = dns_packet_append_uint16(p, 0, NULL);
+ r = dns_packet_append_uint16(p, ((uint16_t) rcode & 0x0FF0) << 4, NULL);
if (r < 0)
goto fail;
@@ -717,9 +720,8 @@ int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, si
goto fail;
/* RDLENGTH */
-
- if (edns0_do) {
- /* If DO is on, also append RFC6975 Algorithm data */
+ if (edns0_do && !DNS_PACKET_QR(p)) {
+ /* If DO is on and this is not a reply, also append RFC6975 Algorithm data */
static const uint8_t rfc6975[] = {
@@ -750,7 +752,6 @@ int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, si
r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL);
} else
r = dns_packet_append_uint16(p, 0, NULL);
-
if (r < 0)
goto fail;
@@ -791,6 +792,7 @@ int dns_packet_truncate_opt(DnsPacket *p) {
}
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) {
+
size_t saved_size, rdlength_offset, end, rdlength, rds;
int r;
@@ -1134,6 +1136,36 @@ fail:
return r;
}
+int dns_packet_append_question(DnsPacket *p, DnsQuestion *q) {
+ DnsResourceKey *key;
+ int r;
+
+ assert(p);
+
+ DNS_QUESTION_FOREACH(key, q) {
+ r = dns_packet_append_key(p, key, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a) {
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(p);
+
+ DNS_ANSWER_FOREACH(rr, a) {
+ r = dns_packet_append_rr(p, rr, NULL, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) {
assert(p);
@@ -2029,8 +2061,10 @@ static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) {
assert(rr->key->type == DNS_TYPE_OPT);
/* Check that the version is 0 */
- if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0)
- return false;
+ if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) {
+ *rfc6975 = false;
+ return true; /* if it's not version 0, it's OK, but we will ignore the OPT field contents */
+ }
p = rr->opt.data;
l = rr->opt.data_size;
@@ -2153,16 +2187,27 @@ int dns_packet_extract(DnsPacket *p) {
continue;
}
- if (has_rfc6975) {
- /* If the OPT RR contains RFC6975 algorithm data, then this is indication that
- * the server just copied the OPT it got from us (which contained that data)
- * back into the reply. If so, then it doesn't properly support EDNS, as
- * RFC6975 makes it very clear that the algorithm data should only be contained
- * in questions, never in replies. Crappy Belkin routers copy the OPT data for
- * example, hence let's detect this so that we downgrade early. */
- log_debug("OPT RR contained RFC6975 data, ignoring.");
- bad_opt = true;
- continue;
+ if (DNS_PACKET_QR(p)) {
+ /* Additional checks for responses */
+
+ if (!DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(rr)) {
+ /* If this is a reply and we don't know the EDNS version then something
+ * is weird... */
+ log_debug("EDNS version newer that our request, bad server.");
+ return -EBADMSG;
+ }
+
+ if (has_rfc6975) {
+ /* If the OPT RR contains RFC6975 algorithm data, then this is indication that
+ * the server just copied the OPT it got from us (which contained that data)
+ * back into the reply. If so, then it doesn't properly support EDNS, as
+ * RFC6975 makes it very clear that the algorithm data should only be contained
+ * in questions, never in replies. Crappy Belkin routers copy the OPT data for
+ * example, hence let's detect this so that we downgrade early. */
+ log_debug("OPT RR contained RFC6975 data, ignoring.");
+ bad_opt = true;
+ continue;
+ }
}
p->opt = dns_resource_record_ref(rr);
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index 416335d0a2..7b7d4e14c9 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -118,6 +118,8 @@ static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) {
#define DNS_PACKET_AD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 5) & 1)
#define DNS_PACKET_CD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 4) & 1)
+#define DNS_PACKET_FLAG_TC (UINT16_C(1) << 9)
+
static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) {
uint16_t rcode;
@@ -126,7 +128,34 @@ static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) {
else
rcode = 0;
- return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 15);
+ return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 0xF);
+}
+
+static inline uint16_t DNS_PACKET_PAYLOAD_SIZE_MAX(DnsPacket *p) {
+
+ /* Returns the advertised maximum datagram size for replies, or the DNS default if there's nothing defined. */
+
+ if (p->opt)
+ return MAX(DNS_PACKET_UNICAST_SIZE_MAX, p->opt->key->class);
+
+ return DNS_PACKET_UNICAST_SIZE_MAX;
+}
+
+static inline bool DNS_PACKET_DO(DnsPacket *p) {
+ if (!p->opt)
+ return false;
+
+ return !!(p->opt->ttl & (1U << 15));
+}
+
+static inline bool DNS_PACKET_VERSION_SUPPORTED(DnsPacket *p) {
+ /* Returns true if this packet is in a version we support. Which means either non-EDNS or EDNS(0), but not EDNS
+ * of any newer versions */
+
+ if (!p->opt)
+ return true;
+
+ return DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(p->opt);
}
/* LLMNR defines some bits differently */
@@ -182,7 +211,9 @@ int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonica
int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start);
int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start);
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start);
-int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start);
+int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, int rcode, size_t *start);
+int dns_packet_append_question(DnsPacket *p, DnsQuestion *q);
+int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a);
void dns_packet_truncate(DnsPacket *p, size_t sz);
int dns_packet_truncate_opt(DnsPacket *p);
@@ -232,7 +263,8 @@ enum {
DNS_RCODE_BADNAME = 20,
DNS_RCODE_BADALG = 21,
DNS_RCODE_BADTRUNC = 22,
- _DNS_RCODE_MAX_DEFINED
+ _DNS_RCODE_MAX_DEFINED,
+ _DNS_RCODE_MAX = 4095 /* 4 bit rcode in the header plus 8 bit rcode in OPT, makes 12 bit */
};
const char* dns_rcode_to_string(int i) _const_;
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
index ea04e58d61..53be18efc6 100644
--- a/src/resolve/resolved-dns-query.c
+++ b/src/resolve/resolved-dns-query.c
@@ -154,6 +154,7 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource
goto gc;
}
+ t->clamp_ttl = c->query->clamp_ttl;
return 1;
gc:
@@ -403,6 +404,16 @@ DnsQuery *dns_query_free(DnsQuery *q) {
sd_bus_message_unref(q->request);
sd_bus_track_unref(q->bus_track);
+ dns_packet_unref(q->request_dns_packet);
+
+ if (q->request_dns_stream) {
+ /* Detach the stream from our query, in case something else keeps a reference to it. */
+ q->request_dns_stream->complete = NULL;
+ q->request_dns_stream->on_packet = NULL;
+ q->request_dns_stream->query = NULL;
+ dns_stream_unref(q->request_dns_stream);
+ }
+
free(q->request_address_string);
if (q->manager) {
@@ -420,7 +431,8 @@ int dns_query_new(
DnsQuery **ret,
DnsQuestion *question_utf8,
DnsQuestion *question_idna,
- int ifindex, uint64_t flags) {
+ int ifindex,
+ uint64_t flags) {
_cleanup_(dns_query_freep) DnsQuery *q = NULL;
DnsResourceKey *key;
@@ -508,7 +520,7 @@ int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) {
assert(q);
assert(auxiliary_for);
- /* Ensure that that the query is not auxiliary yet, and
+ /* Ensure that the query is not auxiliary yet, and
* nothing else is auxiliary to it either */
assert(!q->auxiliary_for);
assert(!q->auxiliary_queries);
diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h
index c2ac02f68b..49a35b846b 100644
--- a/src/resolve/resolved-dns-query.h
+++ b/src/resolve/resolved-dns-query.h
@@ -71,6 +71,10 @@ struct DnsQuery {
* family */
bool suppress_unroutable_family;
+
+ /* If true, the RR TTLs of the answer will be clamped by their current left validity in the cache */
+ bool clamp_ttl;
+
DnsTransactionState state;
unsigned n_cname_redirects;
@@ -95,6 +99,10 @@ struct DnsQuery {
unsigned block_all_complete;
char *request_address_string;
+ /* DNS stub information */
+ DnsPacket *request_dns_packet;
+ DnsStream *request_dns_stream;
+
/* Completion callback */
void (*complete)(DnsQuery* q);
unsigned block_ready;
diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h
index ea41478975..a9a1863b1e 100644
--- a/src/resolve/resolved-dns-question.h
+++ b/src/resolve/resolved-dns-question.h
@@ -56,6 +56,10 @@ static inline unsigned dns_question_size(DnsQuestion *q) {
return q ? q->n_keys : 0;
}
+static inline bool dns_question_isempty(DnsQuestion *q) {
+ return dns_question_size(q) <= 0;
+}
+
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref);
#define _DNS_QUESTION_FOREACH(u, key, q) \
diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c
index 6a29a93a26..5687588a7d 100644
--- a/src/resolve/resolved-dns-rr.c
+++ b/src/resolve/resolved-dns-rr.c
@@ -1532,6 +1532,232 @@ const struct hash_ops dns_resource_record_hash_ops = {
.compare = dns_resource_record_compare_func,
};
+DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr) {
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *copy = NULL;
+ DnsResourceRecord *t;
+
+ assert(rr);
+
+ copy = dns_resource_record_new(rr->key);
+ if (!copy)
+ return NULL;
+
+ copy->ttl = rr->ttl;
+ copy->expiry = rr->expiry;
+ copy->n_skip_labels_signer = rr->n_skip_labels_signer;
+ copy->n_skip_labels_source = rr->n_skip_labels_source;
+ copy->unparseable = rr->unparseable;
+
+ switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) {
+
+ case DNS_TYPE_SRV:
+ copy->srv.priority = rr->srv.priority;
+ copy->srv.weight = rr->srv.weight;
+ copy->srv.port = rr->srv.port;
+ copy->srv.name = strdup(rr->srv.name);
+ if (!copy->srv.name)
+ return NULL;
+ break;
+
+ case DNS_TYPE_PTR:
+ case DNS_TYPE_NS:
+ case DNS_TYPE_CNAME:
+ case DNS_TYPE_DNAME:
+ copy->ptr.name = strdup(rr->ptr.name);
+ if (!copy->ptr.name)
+ return NULL;
+ break;
+
+ case DNS_TYPE_HINFO:
+ copy->hinfo.cpu = strdup(rr->hinfo.cpu);
+ if (!copy->hinfo.cpu)
+ return NULL;
+
+ copy->hinfo.os = strdup(rr->hinfo.os);
+ if(!copy->hinfo.os)
+ return NULL;
+ break;
+
+ case DNS_TYPE_TXT:
+ case DNS_TYPE_SPF:
+ copy->txt.items = dns_txt_item_copy(rr->txt.items);
+ if (!copy->txt.items)
+ return NULL;
+ break;
+
+ case DNS_TYPE_A:
+ copy->a = rr->a;
+ break;
+
+ case DNS_TYPE_AAAA:
+ copy->aaaa = rr->aaaa;
+ break;
+
+ case DNS_TYPE_SOA:
+ copy->soa.mname = strdup(rr->soa.mname);
+ if (!copy->soa.mname)
+ return NULL;
+ copy->soa.rname = strdup(rr->soa.rname);
+ if (!copy->soa.rname)
+ return NULL;
+ copy->soa.serial = rr->soa.serial;
+ copy->soa.refresh = rr->soa.refresh;
+ copy->soa.retry = rr->soa.retry;
+ copy->soa.expire = rr->soa.expire;
+ copy->soa.minimum = rr->soa.minimum;
+ break;
+
+ case DNS_TYPE_MX:
+ copy->mx.priority = rr->mx.priority;
+ copy->mx.exchange = strdup(rr->mx.exchange);
+ if (!copy->mx.exchange)
+ return NULL;
+ break;
+
+ case DNS_TYPE_LOC:
+ copy->loc = rr->loc;
+ break;
+
+ case DNS_TYPE_SSHFP:
+ copy->sshfp.algorithm = rr->sshfp.algorithm;
+ copy->sshfp.fptype = rr->sshfp.fptype;
+ copy->sshfp.fingerprint = memdup(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size);
+ if (!copy->sshfp.fingerprint)
+ return NULL;
+ copy->sshfp.fingerprint_size = rr->sshfp.fingerprint_size;
+ break;
+
+ case DNS_TYPE_DNSKEY:
+ copy->dnskey.flags = rr->dnskey.flags;
+ copy->dnskey.protocol = rr->dnskey.protocol;
+ copy->dnskey.algorithm = rr->dnskey.algorithm;
+ copy->dnskey.key = memdup(rr->dnskey.key, rr->dnskey.key_size);
+ if (!copy->dnskey.key)
+ return NULL;
+ copy->dnskey.key_size = rr->dnskey.key_size;
+ break;
+
+ case DNS_TYPE_RRSIG:
+ copy->rrsig.type_covered = rr->rrsig.type_covered;
+ copy->rrsig.algorithm = rr->rrsig.algorithm;
+ copy->rrsig.labels = rr->rrsig.labels;
+ copy->rrsig.original_ttl = rr->rrsig.original_ttl;
+ copy->rrsig.expiration = rr->rrsig.expiration;
+ copy->rrsig.inception = rr->rrsig.inception;
+ copy->rrsig.key_tag = rr->rrsig.key_tag;
+ copy->rrsig.signer = strdup(rr->rrsig.signer);
+ if (!copy->rrsig.signer)
+ return NULL;
+ copy->rrsig.signature = memdup(rr->rrsig.signature, rr->rrsig.signature_size);
+ if (!copy->rrsig.signature)
+ return NULL;
+ copy->rrsig.signature_size = rr->rrsig.signature_size;
+ break;
+
+ case DNS_TYPE_NSEC:
+ copy->nsec.next_domain_name = strdup(rr->nsec.next_domain_name);
+ if (!copy->nsec.next_domain_name)
+ return NULL;
+ copy->nsec.types = bitmap_copy(rr->nsec.types);
+ if (!copy->nsec.types)
+ return NULL;
+ break;
+
+ case DNS_TYPE_DS:
+ copy->ds.key_tag = rr->ds.key_tag;
+ copy->ds.algorithm = rr->ds.algorithm;
+ copy->ds.digest_type = rr->ds.digest_type;
+ copy->ds.digest = memdup(rr->ds.digest, rr->ds.digest_size);
+ if (!copy->ds.digest)
+ return NULL;
+ copy->ds.digest_size = rr->ds.digest_size;
+ break;
+
+ case DNS_TYPE_NSEC3:
+ copy->nsec3.algorithm = rr->nsec3.algorithm;
+ copy->nsec3.flags = rr->nsec3.flags;
+ copy->nsec3.iterations = rr->nsec3.iterations;
+ copy->nsec3.salt = memdup(rr->nsec3.salt, rr->nsec3.salt_size);
+ if (!copy->nsec3.salt)
+ return NULL;
+ copy->nsec3.salt_size = rr->nsec3.salt_size;
+ copy->nsec3.next_hashed_name = memdup(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size);
+ if (!copy->nsec3.next_hashed_name_size)
+ return NULL;
+ copy->nsec3.next_hashed_name_size = rr->nsec3.next_hashed_name_size;
+ copy->nsec3.types = bitmap_copy(rr->nsec3.types);
+ if (!copy->nsec3.types)
+ return NULL;
+ break;
+
+ case DNS_TYPE_TLSA:
+ copy->tlsa.cert_usage = rr->tlsa.cert_usage;
+ copy->tlsa.selector = rr->tlsa.selector;
+ copy->tlsa.matching_type = rr->tlsa.matching_type;
+ copy->tlsa.data = memdup(rr->tlsa.data, rr->tlsa.data_size);
+ if (!copy->tlsa.data)
+ return NULL;
+ copy->tlsa.data_size = rr->tlsa.data_size;
+ break;
+
+ case DNS_TYPE_CAA:
+ copy->caa.flags = rr->caa.flags;
+ copy->caa.tag = strdup(rr->caa.tag);
+ if (!copy->caa.tag)
+ return NULL;
+ copy->caa.value = memdup(rr->caa.value, rr->caa.value_size);
+ if (!copy->caa.value)
+ return NULL;
+ copy->caa.value_size = rr->caa.value_size;
+ break;
+
+ case DNS_TYPE_OPT:
+ default:
+ copy->generic.data = memdup(rr->generic.data, rr->generic.data_size);
+ if (!copy->generic.data)
+ return NULL;
+ copy->generic.data_size = rr->generic.data_size;
+ break;
+ }
+
+ t = copy;
+ copy = NULL;
+
+ return t;
+}
+
+int dns_resource_record_clamp_ttl(DnsResourceRecord **rr, uint32_t max_ttl) {
+ DnsResourceRecord *old_rr, *new_rr;
+ uint32_t new_ttl;
+
+ assert(rr);
+ old_rr = *rr;
+
+ if (old_rr->key->type == DNS_TYPE_OPT)
+ return -EINVAL;
+
+ new_ttl = MIN(old_rr->ttl, max_ttl);
+ if (new_ttl == old_rr->ttl)
+ return 0;
+
+ if (old_rr->n_ref == 1) {
+ /* Patch in place */
+ old_rr->ttl = new_ttl;
+ return 1;
+ }
+
+ new_rr = dns_resource_record_copy(old_rr);
+ if (!new_rr)
+ return -ENOMEM;
+
+ new_rr->ttl = new_ttl;
+
+ dns_resource_record_unref(*rr);
+ *rr = new_rr;
+
+ return 1;
+}
+
DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) {
DnsTxtItem *n;
@@ -1564,6 +1790,25 @@ bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) {
return dns_txt_item_equal(a->items_next, b->items_next);
}
+DnsTxtItem *dns_txt_item_copy(DnsTxtItem *first) {
+ DnsTxtItem *i, *copy = NULL, *end = NULL;
+
+ LIST_FOREACH(items, i, first) {
+ DnsTxtItem *j;
+
+ j = memdup(i, offsetof(DnsTxtItem, data) + i->length + 1);
+ if (!j) {
+ dns_txt_item_free_all(copy);
+ return NULL;
+ }
+
+ LIST_INSERT_AFTER(items, copy, end, j);
+ end = j;
+ }
+
+ return copy;
+}
+
static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = {
/* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */
[DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5",
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index 020a2abd77..42d39a1251 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -282,6 +282,13 @@ static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) {
return rr->wire_format_size - rr->wire_format_rdata_offset;
}
+static inline uint8_t DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(DnsResourceRecord *rr) {
+ assert(rr);
+ assert(rr->key->type == DNS_TYPE_OPT);
+
+ return ((rr->ttl >> 16) & 0xFF) == 0;
+}
+
DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name);
DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname);
int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name);
@@ -318,6 +325,7 @@ int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const u
int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name);
int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b);
const char* dns_resource_record_to_string(DnsResourceRecord *rr);
+DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref);
int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical);
@@ -327,8 +335,11 @@ int dns_resource_record_source(DnsResourceRecord *rr, const char **ret);
int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone);
int dns_resource_record_is_synthetic(DnsResourceRecord *rr);
+int dns_resource_record_clamp_ttl(DnsResourceRecord **rr, uint32_t max_ttl);
+
DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i);
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);
+DnsTxtItem *dns_txt_item_copy(DnsTxtItem *i);
void dns_resource_record_hash_func(const void *i, struct siphash *state);
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index 66e4585c18..ed0c6aa105 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -232,7 +232,7 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
if (fd < 0)
return fd;
- r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p);
+ r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, NULL, p);
if (r < 0)
return r;
@@ -257,7 +257,7 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
if (fd < 0)
return fd;
- r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p);
+ r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, NULL, p);
if (r < 0)
return r;
@@ -307,7 +307,7 @@ static int dns_scope_socket(
union sockaddr_union sa = {};
socklen_t salen;
static const int one = 1;
- int ret, r;
+ int ret, r, ifindex;
assert(s);
@@ -315,6 +315,8 @@ static int dns_scope_socket(
assert(family == AF_UNSPEC);
assert(!address);
+ ifindex = dns_server_ifindex(server);
+
sa.sa.sa_family = server->family;
if (server->family == AF_INET) {
sa.in.sin_port = htobe16(port);
@@ -323,7 +325,7 @@ static int dns_scope_socket(
} else if (server->family == AF_INET6) {
sa.in6.sin6_port = htobe16(port);
sa.in6.sin6_addr = server->address.in6;
- sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0;
+ sa.in6.sin6_scope_id = ifindex;
salen = sizeof(sa.in6);
} else
return -EAFNOSUPPORT;
@@ -332,6 +334,7 @@ static int dns_scope_socket(
assert(address);
sa.sa.sa_family = family;
+ ifindex = s->link ? s->link->ifindex : 0;
if (family == AF_INET) {
sa.in.sin_port = htobe16(port);
@@ -340,7 +343,7 @@ static int dns_scope_socket(
} else if (family == AF_INET6) {
sa.in6.sin6_port = htobe16(port);
sa.in6.sin6_addr = address->in6;
- sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0;
+ sa.in6.sin6_scope_id = ifindex;
salen = sizeof(sa.in6);
} else
return -EAFNOSUPPORT;
@@ -357,14 +360,14 @@ static int dns_scope_socket(
}
if (s->link) {
- uint32_t ifindex = htobe32(s->link->ifindex);
+ be32_t ifindex_be = htobe32(ifindex);
if (sa.sa.sa_family == AF_INET) {
- r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex));
+ r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex_be, sizeof(ifindex_be));
if (r < 0)
return -errno;
} else if (sa.sa.sa_family == AF_INET6) {
- r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex));
+ r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex_be, sizeof(ifindex_be));
if (r < 0)
return -errno;
}
@@ -575,6 +578,7 @@ static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in
}
int dns_scope_llmnr_membership(DnsScope *s, bool b) {
+ assert(s);
if (s->protocol != DNS_PROTOCOL_LLMNR)
return 0;
@@ -583,6 +587,7 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b) {
}
int dns_scope_mdns_membership(DnsScope *s, bool b) {
+ assert(s);
if (s->protocol != DNS_PROTOCOL_MDNS)
return 0;
@@ -601,15 +606,14 @@ static int dns_scope_make_reply_packet(
DnsPacket **ret) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
- unsigned i;
int r;
assert(s);
assert(ret);
- if ((!q || q->n_keys <= 0)
- && (!answer || answer->n_rrs <= 0)
- && (!soa || soa->n_rrs <= 0))
+ if (dns_question_isempty(q) &&
+ dns_answer_isempty(answer) &&
+ dns_answer_isempty(soa))
return -EINVAL;
r = dns_packet_new(&p, s->protocol, 0);
@@ -628,35 +632,20 @@ static int dns_scope_make_reply_packet(
0 /* (cd) */,
rcode));
- if (q) {
- for (i = 0; i < q->n_keys; i++) {
- r = dns_packet_append_key(p, q->keys[i], NULL);
- if (r < 0)
- return r;
- }
-
- DNS_PACKET_HEADER(p)->qdcount = htobe16(q->n_keys);
- }
-
- if (answer) {
- for (i = 0; i < answer->n_rrs; i++) {
- r = dns_packet_append_rr(p, answer->items[i].rr, NULL, NULL);
- if (r < 0)
- return r;
- }
-
- DNS_PACKET_HEADER(p)->ancount = htobe16(answer->n_rrs);
- }
+ r = dns_packet_append_question(p, q);
+ if (r < 0)
+ return r;
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q));
- if (soa) {
- for (i = 0; i < soa->n_rrs; i++) {
- r = dns_packet_append_rr(p, soa->items[i].rr, NULL, NULL);
- if (r < 0)
- return r;
- }
+ r = dns_packet_append_answer(p, answer);
+ if (r < 0)
+ return r;
+ DNS_PACKET_HEADER(p)->ancount = htobe16(dns_answer_size(answer));
- DNS_PACKET_HEADER(p)->arcount = htobe16(soa->n_rrs);
- }
+ r = dns_packet_append_answer(p, soa);
+ if (r < 0)
+ return r;
+ DNS_PACKET_HEADER(p)->arcount = htobe16(dns_answer_size(soa));
*ret = p;
p = NULL;
@@ -665,25 +654,25 @@ static int dns_scope_make_reply_packet(
}
static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) {
- unsigned n;
+ DnsResourceRecord *rr;
+ DnsResourceKey *key;
assert(s);
assert(p);
- if (p->question)
- for (n = 0; n < p->question->n_keys; n++)
- dns_zone_verify_conflicts(&s->zone, p->question->keys[n]);
- if (p->answer)
- for (n = 0; n < p->answer->n_rrs; n++)
- dns_zone_verify_conflicts(&s->zone, p->answer->items[n].rr->key);
+ DNS_QUESTION_FOREACH(key, p->question)
+ dns_zone_verify_conflicts(&s->zone, key);
+
+ DNS_ANSWER_FOREACH(rr, p->answer)
+ dns_zone_verify_conflicts(&s->zone, rr->key);
}
void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
- _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
+ _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
DnsResourceKey *key = NULL;
bool tentative = false;
- int r, fd;
+ int r;
assert(s);
assert(p);
@@ -705,7 +694,7 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
r = dns_packet_extract(p);
if (r < 0) {
- log_debug_errno(r, "Failed to extract resources from incoming packet: %m");
+ log_debug_errno(r, "Failed to extract resource records from incoming packet: %m");
return;
}
@@ -715,10 +704,10 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
return;
}
- assert(p->question->n_keys == 1);
+ assert(dns_question_size(p->question) == 1);
key = p->question->keys[0];
- r = dns_zone_lookup(&s->zone, key, &answer, &soa, &tentative);
+ r = dns_zone_lookup(&s->zone, key, 0, &answer, &soa, &tentative);
if (r < 0) {
log_debug_errno(r, "Failed to lookup key: %m");
return;
@@ -735,9 +724,21 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
return;
}
- if (stream)
+ if (stream) {
r = dns_stream_write_packet(stream, reply);
- else {
+ if (r < 0) {
+ log_debug_errno(r, "Failed to enqueue reply packet: %m");
+ return;
+ }
+
+ /* Let's take an extra reference on this stream, so that it stays around after returning. The reference
+ * will be dangling until the stream is disconnected, and the default completion handler of the stream
+ * will then unref the stream and destroy it */
+ if (DNS_STREAM_QUEUED(stream))
+ dns_stream_ref(stream);
+ } else {
+ int fd;
+
if (!ratelimit_test(&s->ratelimit))
return;
@@ -759,12 +760,11 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
* verified uniqueness for all records. Also see RFC
* 4795, Section 2.7 */
- r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, reply);
- }
-
- if (r < 0) {
- log_debug_errno(r, "Failed to send reply packet: %m");
- return;
+ r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, NULL, reply);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to send reply packet: %m");
+ return;
+ }
}
}
@@ -1026,3 +1026,12 @@ bool dns_scope_network_good(DnsScope *s) {
return manager_routable(s->manager, AF_UNSPEC);
}
+
+int dns_scope_ifindex(DnsScope *s) {
+ assert(s);
+
+ if (s->link)
+ return s->link->ifindex;
+
+ return 0;
+}
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index 291e5817d0..538bc61f81 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -107,3 +107,5 @@ DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s);
bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name);
bool dns_scope_network_good(DnsScope *s);
+
+int dns_scope_ifindex(DnsScope *s);
diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c
index 3095c042db..9b7b471600 100644
--- a/src/resolve/resolved-dns-server.c
+++ b/src/resolve/resolved-dns-server.c
@@ -21,6 +21,7 @@
#include "alloc-util.h"
#include "resolved-dns-server.h"
+#include "resolved-dns-stub.h"
#include "resolved-resolv-conf.h"
#include "siphash24.h"
#include "string-table.h"
@@ -43,7 +44,8 @@ int dns_server_new(
DnsServerType type,
Link *l,
int family,
- const union in_addr_union *in_addr) {
+ const union in_addr_union *in_addr,
+ int ifindex) {
DnsServer *s;
@@ -75,6 +77,7 @@ int dns_server_new(
s->type = type;
s->family = family;
s->address = *in_addr;
+ s->ifindex = ifindex;
s->resend_timeout = DNS_TIMEOUT_MIN_USEC;
switch (type) {
@@ -242,6 +245,26 @@ static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) {
assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0);
}
+static void dns_server_reset_counters(DnsServer *s) {
+ assert(s);
+
+ s->n_failed_udp = 0;
+ s->n_failed_tcp = 0;
+ s->packet_truncated = false;
+ s->verified_usec = 0;
+
+ /* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the
+ * grace period ends, but not when lowering the possible feature level, as a lower level feature level should
+ * not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's
+ * indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that
+ * either.
+ *
+ * This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A),
+ * but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not
+ * reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be
+ * incomplete. */
+}
+
void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size) {
assert(s);
@@ -302,17 +325,6 @@ void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel le
s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC);
}
-void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) {
- assert(s);
-
- /* Invoked whenever we get a FORMERR, SERVFAIL or NOTIMP rcode from a server. */
-
- if (s->possible_feature_level != level)
- return;
-
- s->packet_failed = true;
-}
-
void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) {
assert(s);
@@ -350,6 +362,24 @@ void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level) {
s->packet_bad_opt = true;
}
+void dns_server_packet_rcode_downgrade(DnsServer *s, DnsServerFeatureLevel level) {
+ assert(s);
+
+ /* Invoked whenever we got a FORMERR, SERVFAIL or NOTIMP rcode from a server and downgrading the feature level
+ * for the transaction made it go away. In this case we immediately downgrade to the feature level that made
+ * things work. */
+
+ if (s->verified_feature_level > level)
+ s->verified_feature_level = level;
+
+ if (s->possible_feature_level > level) {
+ s->possible_feature_level = level;
+ dns_server_reset_counters(s);
+ }
+
+ log_debug("Downgrading transaction feature level fixed an RCODE error, downgrading server %s too.", dns_server_string(s));
+}
+
static bool dns_server_grace_period_expired(DnsServer *s) {
usec_t ts;
@@ -369,27 +399,6 @@ static bool dns_server_grace_period_expired(DnsServer *s) {
return true;
}
-static void dns_server_reset_counters(DnsServer *s) {
- assert(s);
-
- s->n_failed_udp = 0;
- s->n_failed_tcp = 0;
- s->packet_failed = false;
- s->packet_truncated = false;
- s->verified_usec = 0;
-
- /* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the
- * grace period ends, but not when lowering the possible feature level, as a lower level feature level should
- * not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's
- * indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that
- * either.
- *
- * This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A),
- * but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not
- * reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be
- * incomplete. */
-}
-
DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
assert(s);
@@ -452,16 +461,6 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
log_debug("Lost too many UDP packets, downgrading feature level...");
s->possible_feature_level--;
- } else if (s->packet_failed &&
- s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
-
- /* We got a failure packet, and are at a feature level above UDP. Note that in this case we
- * downgrade no further than UDP, under the assumption that a failure packet indicates an
- * incompatible packet contents, but not a problem with the transport. */
-
- log_debug("Got server failure, downgrading feature level...");
- s->possible_feature_level--;
-
} else if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
s->packet_truncated &&
s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
@@ -515,14 +514,27 @@ int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeature
else
packet_size = server->received_udp_packet_max;
- return dns_packet_append_opt(packet, packet_size, edns_do, NULL);
+ return dns_packet_append_opt(packet, packet_size, edns_do, 0, NULL);
+}
+
+int dns_server_ifindex(const DnsServer *s) {
+ assert(s);
+
+ /* The link ifindex always takes precedence */
+ if (s->link)
+ return s->link->ifindex;
+
+ if (s->ifindex > 0)
+ return s->ifindex;
+
+ return 0;
}
const char *dns_server_string(DnsServer *server) {
assert(server);
if (!server->server_string)
- (void) in_addr_to_string(server->family, &server->address, &server->server_string);
+ (void) in_addr_ifindex_to_string(server->family, &server->address, dns_server_ifindex(server), &server->server_string);
return strna(server->server_string);
}
@@ -571,17 +583,28 @@ static void dns_server_hash_func(const void *p, struct siphash *state) {
siphash24_compress(&s->family, sizeof(s->family), state);
siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state);
+ siphash24_compress(&s->ifindex, sizeof(s->ifindex), state);
}
static int dns_server_compare_func(const void *a, const void *b) {
const DnsServer *x = a, *y = b;
+ int r;
if (x->family < y->family)
return -1;
if (x->family > y->family)
return 1;
- return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family));
+ r = memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family));
+ if (r != 0)
+ return r;
+
+ if (x->ifindex < y->ifindex)
+ return -1;
+ if (x->ifindex > y->ifindex)
+ return 1;
+
+ return 0;
}
const struct hash_ops dns_server_hash_ops = {
@@ -623,11 +646,11 @@ void dns_server_mark_all(DnsServer *first) {
dns_server_mark_all(first->servers_next);
}
-DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr) {
+DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, int ifindex) {
DnsServer *s;
LIST_FOREACH(servers, s, first)
- if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0)
+ if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0 && s->ifindex == ifindex)
return s;
return NULL;
@@ -724,6 +747,19 @@ void manager_next_dns_server(Manager *m) {
manager_set_dns_server(m, m->dns_servers);
}
+bool dns_server_address_valid(int family, const union in_addr_union *sa) {
+
+ /* Refuses the 0 IP addresses as well as 127.0.0.53 (which is our own DNS stub) */
+
+ if (in_addr_is_null(family, sa))
+ return false;
+
+ if (family == AF_INET && sa->in.s_addr == htobe32(INADDR_DNS_STUB))
+ return false;
+
+ return true;
+}
+
static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = {
[DNS_SERVER_SYSTEM] = "system",
[DNS_SERVER_FALLBACK] = "fallback",
diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h
index 9f4a69c37a..c1732faffd 100644
--- a/src/resolve/resolved-dns-server.h
+++ b/src/resolve/resolved-dns-server.h
@@ -62,6 +62,7 @@ struct DnsServer {
int family;
union in_addr_union address;
+ int ifindex; /* for IPv6 link-local DNS servers */
char *server_string;
@@ -76,7 +77,6 @@ struct DnsServer {
unsigned n_failed_udp;
unsigned n_failed_tcp;
- bool packet_failed:1;
bool packet_truncated:1;
bool packet_bad_opt:1;
bool packet_rrsig_missing:1;
@@ -101,7 +101,8 @@ int dns_server_new(
DnsServerType type,
Link *link,
int family,
- const union in_addr_union *address);
+ const union in_addr_union *address,
+ int ifindex);
DnsServer* dns_server_ref(DnsServer *s);
DnsServer* dns_server_unref(DnsServer *s);
@@ -111,22 +112,23 @@ void dns_server_move_back_and_unmark(DnsServer *s);
void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size);
void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec);
-void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level);
+void dns_server_packet_rcode_downgrade(DnsServer *s, DnsServerFeatureLevel level);
DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s);
int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level);
const char *dns_server_string(DnsServer *server);
+int dns_server_ifindex(const DnsServer *s);
bool dns_server_dnssec_supported(DnsServer *server);
void dns_server_warn_downgrade(DnsServer *server);
-DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr);
+DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, int ifindex);
void dns_server_unlink_all(DnsServer *first);
void dns_server_unlink_marked(DnsServer *first);
@@ -138,6 +140,8 @@ DnsServer *manager_set_dns_server(Manager *m, DnsServer *s);
DnsServer *manager_get_dns_server(Manager *m);
void manager_next_dns_server(Manager *m);
+bool dns_server_address_valid(int family, const union in_addr_union *sa);
+
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref);
extern const struct hash_ops dns_server_hash_ops;
diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c
index a1040aeff4..dd0e0b90e3 100644
--- a/src/resolve/resolved-dns-stream.c
+++ b/src/resolve/resolved-dns-stream.c
@@ -56,8 +56,8 @@ static int dns_stream_complete(DnsStream *s, int error) {
if (s->complete)
s->complete(s, error);
- else
- dns_stream_free(s);
+ else /* the default action if no completion function is set is to close the stream */
+ dns_stream_unref(s);
return 0;
}
@@ -323,10 +323,16 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use
return 0;
}
-DnsStream *dns_stream_free(DnsStream *s) {
+DnsStream *dns_stream_unref(DnsStream *s) {
if (!s)
return NULL;
+ assert(s->n_ref > 0);
+ s->n_ref--;
+
+ if (s->n_ref > 0)
+ return NULL;
+
dns_stream_stop(s);
if (s->manager) {
@@ -339,13 +345,23 @@ DnsStream *dns_stream_free(DnsStream *s) {
free(s);
- return 0;
+ return NULL;
}
-DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_unref);
+
+DnsStream *dns_stream_ref(DnsStream *s) {
+ if (!s)
+ return NULL;
+
+ assert(s->n_ref > 0);
+ s->n_ref++;
+
+ return s;
+}
int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
- _cleanup_(dns_stream_freep) DnsStream *s = NULL;
+ _cleanup_(dns_stream_unrefp) DnsStream *s = NULL;
int r;
assert(m);
@@ -358,6 +374,7 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
if (!s)
return -ENOMEM;
+ s->n_ref = 1;
s->fd = -1;
s->protocol = protocol;
diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h
index 5ccc842249..e6569678fa 100644
--- a/src/resolve/resolved-dns-stream.h
+++ b/src/resolve/resolved-dns-stream.h
@@ -26,8 +26,16 @@ typedef struct DnsStream DnsStream;
#include "resolved-dns-packet.h"
#include "resolved-dns-transaction.h"
+/* Streams are used by three subsystems:
+ *
+ * 1. The normal transaction logic when doing a DNS or LLMNR lookup via TCP
+ * 2. The LLMNR logic when accepting a TCP-based lookup
+ * 3. The DNS stub logic when accepting a TCP-based lookup
+ */
+
struct DnsStream {
Manager *manager;
+ int n_ref;
DnsProtocol protocol;
@@ -50,12 +58,23 @@ struct DnsStream {
int (*on_packet)(DnsStream *s);
int (*complete)(DnsStream *s, int error);
- DnsTransaction *transaction;
+ DnsTransaction *transaction; /* when used by the transaction logic */
+ DnsQuery *query; /* when used by the DNS stub logic */
LIST_FIELDS(DnsStream, streams);
};
int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd);
-DnsStream *dns_stream_free(DnsStream *s);
+DnsStream *dns_stream_unref(DnsStream *s);
+DnsStream *dns_stream_ref(DnsStream *s);
int dns_stream_write_packet(DnsStream *s, DnsPacket *p);
+
+static inline bool DNS_STREAM_QUEUED(DnsStream *s) {
+ assert(s);
+
+ if (s->fd < 0) /* already stopped? */
+ return false;
+
+ return !!s->write_packet;
+}
diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c
new file mode 100644
index 0000000000..d263cedcd9
--- /dev/null
+++ b/src/resolve/resolved-dns-stub.c
@@ -0,0 +1,572 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "fd-util.h"
+#include "resolved-dns-stub.h"
+#include "socket-util.h"
+
+/* The MTU of the loopback device is 64K on Linux, advertise that as maximum datagram size, but subtract the Ethernet,
+ * IP and UDP header sizes */
+#define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U)
+
+static int dns_stub_make_reply_packet(
+ uint16_t id,
+ int rcode,
+ DnsQuestion *q,
+ DnsAnswer *answer,
+ bool add_opt, /* add an OPT RR to this packet */
+ bool edns0_do, /* set the EDNS0 DNSSEC OK bit */
+ bool ad, /* set the DNSSEC authenticated data bit */
+ DnsPacket **ret) {
+
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ DnsResourceRecord *rr;
+ unsigned c = 0;
+ int r;
+
+ /* Note that we don't bother with any additional RRs, as this is stub is for local lookups only, and hence
+ * roundtrips aren't expensive. */
+
+ r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
+ if (r < 0)
+ return r;
+
+ /* If the client didn't do EDNS, clamp the rcode to 4 bit */
+ if (!add_opt && rcode > 0xF)
+ rcode = DNS_RCODE_SERVFAIL;
+
+ DNS_PACKET_HEADER(p)->id = id;
+ DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
+ 1 /* qr */,
+ 0 /* opcode */,
+ 0 /* aa */,
+ 0 /* tc */,
+ 1 /* rd */,
+ 1 /* ra */,
+ ad /* ad */,
+ 0 /* cd */,
+ rcode));
+
+ r = dns_packet_append_question(p, q);
+ if (r < 0)
+ return r;
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q));
+
+ DNS_ANSWER_FOREACH(rr, answer) {
+ r = dns_question_matches_rr(q, rr, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto add;
+
+ r = dns_question_matches_cname_or_dname(q, rr, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto add;
+
+ continue;
+ add:
+ r = dns_packet_append_rr(p, rr, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ c++;
+ }
+ DNS_PACKET_HEADER(p)->ancount = htobe16(c);
+
+ if (add_opt) {
+ r = dns_packet_append_opt(p, ADVERTISE_DATAGRAM_SIZE_MAX, edns0_do, rcode, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = p;
+ p = NULL;
+
+ return 0;
+}
+
+static void dns_stub_detach_stream(DnsStream *s) {
+ assert(s);
+
+ s->complete = NULL;
+ s->on_packet = NULL;
+ s->query = NULL;
+}
+
+static int dns_stub_send(Manager *m, DnsStream *s, DnsPacket *p, DnsPacket *reply) {
+ int r;
+
+ assert(m);
+ assert(p);
+ assert(reply);
+
+ if (s)
+ r = dns_stream_write_packet(s, reply);
+ else {
+ int fd;
+
+ /* Truncate the message to the right size */
+ if (reply->size > DNS_PACKET_PAYLOAD_SIZE_MAX(p)) {
+ dns_packet_truncate(reply, DNS_PACKET_UNICAST_SIZE_MAX);
+ DNS_PACKET_HEADER(reply)->flags = htobe16(be16toh(DNS_PACKET_HEADER(reply)->flags) | DNS_PACKET_FLAG_TC);
+ }
+
+ fd = manager_dns_stub_udp_fd(m);
+ if (fd < 0)
+ return log_debug_errno(fd, "Failed to get reply socket: %m");
+
+ /* Note that it is essential here that we explicitly choose the source IP address for this packet. This
+ * is because otherwise the kernel will choose it automatically based on the routing table and will
+ * thus pick 127.0.0.1 rather than 127.0.0.53. */
+
+ r = manager_send(m, fd, LOOPBACK_IFINDEX, p->family, &p->sender, p->sender_port, &p->destination, reply);
+ }
+ if (r < 0)
+ return log_debug_errno(r, "Failed to send reply packet: %m");
+
+ return 0;
+}
+
+static int dns_stub_send_failure(Manager *m, DnsStream *s, DnsPacket *p, int rcode) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
+ int r;
+
+ assert(m);
+ assert(p);
+
+ r = dns_stub_make_reply_packet(DNS_PACKET_ID(p), rcode, p->question, NULL, !!p->opt, DNS_PACKET_DO(p), false, &reply);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to build failure packet: %m");
+
+ return dns_stub_send(m, s, p, reply);
+}
+
+static void dns_stub_query_complete(DnsQuery *q) {
+ int r;
+
+ assert(q);
+ assert(q->request_dns_packet);
+
+ switch (q->state) {
+
+ case DNS_TRANSACTION_SUCCESS: {
+ _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
+
+ r = dns_stub_make_reply_packet(
+ DNS_PACKET_ID(q->request_dns_packet),
+ q->answer_rcode,
+ q->question_idna,
+ q->answer,
+ !!q->request_dns_packet->opt,
+ DNS_PACKET_DO(q->request_dns_packet),
+ DNS_PACKET_DO(q->request_dns_packet) && q->answer_authenticated,
+ &reply);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to build reply packet: %m");
+ break;
+ }
+
+ (void) dns_stub_send(q->manager, q->request_dns_stream, q->request_dns_packet, reply);
+ break;
+ }
+
+ case DNS_TRANSACTION_RCODE_FAILURE:
+ (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, q->answer_rcode);
+ break;
+
+ case DNS_TRANSACTION_NOT_FOUND:
+ (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN);
+ break;
+
+ case DNS_TRANSACTION_TIMEOUT:
+ case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED:
+ /* Propagate a timeout as a no packet, i.e. that the client also gets a timeout */
+ break;
+
+ case DNS_TRANSACTION_NO_SERVERS:
+ case DNS_TRANSACTION_INVALID_REPLY:
+ case DNS_TRANSACTION_ERRNO:
+ case DNS_TRANSACTION_ABORTED:
+ case DNS_TRANSACTION_DNSSEC_FAILED:
+ case DNS_TRANSACTION_NO_TRUST_ANCHOR:
+ case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED:
+ case DNS_TRANSACTION_NETWORK_DOWN:
+ (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL);
+ break;
+
+ case DNS_TRANSACTION_NULL:
+ case DNS_TRANSACTION_PENDING:
+ case DNS_TRANSACTION_VALIDATING:
+ default:
+ assert_not_reached("Impossible state");
+ }
+
+ /* If there's a packet to write set, let's leave the stream around */
+ if (q->request_dns_stream && DNS_STREAM_QUEUED(q->request_dns_stream)) {
+
+ /* Detach the stream from our query (make it an orphan), but do not drop the reference to it. The
+ * default completion action of the stream will drop the reference. */
+
+ dns_stub_detach_stream(q->request_dns_stream);
+ q->request_dns_stream = NULL;
+ }
+
+ dns_query_free(q);
+}
+
+static int dns_stub_stream_complete(DnsStream *s, int error) {
+ assert(s);
+
+ log_debug_errno(error, "DNS TCP connection terminated, destroying query: %m");
+
+ assert(s->query);
+ dns_query_free(s->query);
+
+ return 0;
+}
+
+static void dns_stub_process_query(Manager *m, DnsStream *s, DnsPacket *p) {
+ DnsQuery *q = NULL;
+ int r;
+
+ assert(m);
+ assert(p);
+ assert(p->protocol == DNS_PROTOCOL_DNS);
+
+ /* Takes ownership of the *s stream object */
+
+ if (in_addr_is_localhost(p->family, &p->sender) <= 0 ||
+ in_addr_is_localhost(p->family, &p->destination) <= 0) {
+ log_error("Got packet on unexpected IP range, refusing.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL);
+ goto fail;
+ }
+
+ r = dns_packet_extract(p);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract resources from incoming packet, ignoring packet: %m");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_FORMERR);
+ goto fail;
+ }
+
+ if (!DNS_PACKET_VERSION_SUPPORTED(p)) {
+ log_debug("Got EDNS OPT field with unsupported version number.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_BADVERS);
+ goto fail;
+ }
+
+ if (dns_type_is_obsolete(p->question->keys[0]->type)) {
+ log_debug("Got message with obsolete key type, refusing.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP);
+ goto fail;
+ }
+
+ if (dns_type_is_zone_transer(p->question->keys[0]->type)) {
+ log_debug("Got request for zone transfer, refusing.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP);
+ goto fail;
+ }
+
+ if (!DNS_PACKET_RD(p)) {
+ /* If the "rd" bit is off (i.e. recursion was not requested), then refuse operation */
+ log_debug("Got request with recursion disabled, refusing.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_REFUSED);
+ goto fail;
+ }
+
+ if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) {
+ log_debug("Got request with DNSSEC CD bit set, refusing.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP);
+ goto fail;
+ }
+
+ r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_NO_CNAME);
+ if (r < 0) {
+ log_error_errno(r, "Failed to generate query object: %m");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL);
+ goto fail;
+ }
+
+ /* Request that the TTL is corrected by the cached time for this lookup, so that we return vaguely useful TTLs */
+ q->clamp_ttl = true;
+
+ q->request_dns_packet = dns_packet_ref(p);
+ q->request_dns_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */
+ q->complete = dns_stub_query_complete;
+
+ if (s) {
+ s->on_packet = NULL;
+ s->complete = dns_stub_stream_complete;
+ s->query = q;
+ }
+
+ r = dns_query_go(q);
+ if (r < 0) {
+ log_error_errno(r, "Failed to start query: %m");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL);
+ goto fail;
+ }
+
+ log_info("Processing query...");
+ return;
+
+fail:
+ if (s && DNS_STREAM_QUEUED(s))
+ dns_stub_detach_stream(s);
+
+ dns_query_free(q);
+}
+
+static int on_dns_stub_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ Manager *m = userdata;
+ int r;
+
+ r = manager_recv(m, fd, DNS_PROTOCOL_DNS, &p);
+ if (r <= 0)
+ return r;
+
+ if (dns_packet_validate_query(p) > 0) {
+ log_debug("Got DNS stub UDP query packet for id %u", DNS_PACKET_ID(p));
+
+ dns_stub_process_query(m, NULL, p);
+ } else
+ log_debug("Invalid DNS stub UDP packet, ignoring.");
+
+ return 0;
+}
+
+int manager_dns_stub_udp_fd(Manager *m) {
+ static const int one = 1;
+
+ union sockaddr_union sa = {
+ .in.sin_family = AF_INET,
+ .in.sin_port = htobe16(53),
+ .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB),
+ };
+
+ int r;
+
+ if (m->dns_stub_udp_fd >= 0)
+ return m->dns_stub_udp_fd;
+
+ m->dns_stub_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->dns_stub_udp_fd < 0)
+ return -errno;
+
+ r = setsockopt(m->dns_stub_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->dns_stub_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->dns_stub_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ /* Make sure no traffic from outside the local host can leak to onto this socket */
+ r = setsockopt(m->dns_stub_udp_fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3);
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = bind(m->dns_stub_udp_fd, &sa.sa, sizeof(sa.in));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = sd_event_add_io(m->event, &m->dns_stub_udp_event_source, m->dns_stub_udp_fd, EPOLLIN, on_dns_stub_packet, m);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(m->dns_stub_udp_event_source, "dns-stub-udp");
+
+ return m->dns_stub_udp_fd;
+
+fail:
+ m->dns_stub_udp_fd = safe_close(m->dns_stub_udp_fd);
+ return r;
+}
+
+static int on_dns_stub_stream_packet(DnsStream *s) {
+ assert(s);
+ assert(s->read_packet);
+
+ if (dns_packet_validate_query(s->read_packet) > 0) {
+ log_debug("Got DNS stub TCP query packet for id %u", DNS_PACKET_ID(s->read_packet));
+
+ dns_stub_process_query(s->manager, s, s->read_packet);
+ } else
+ log_debug("Invalid DNS stub TCP packet, ignoring.");
+
+ /* Drop the reference to the stream. Either a query was created and added its own reference to the stream now,
+ * or that didn't happen in which case we want to free the stream */
+ dns_stream_unref(s);
+
+ return 0;
+}
+
+static int on_dns_stub_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ DnsStream *stream;
+ Manager *m = userdata;
+ int cfd, r;
+
+ cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
+ if (cfd < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+
+ return -errno;
+ }
+
+ r = dns_stream_new(m, &stream, DNS_PROTOCOL_DNS, cfd);
+ if (r < 0) {
+ safe_close(cfd);
+ return r;
+ }
+
+ stream->on_packet = on_dns_stub_stream_packet;
+
+ /* We let the reference to the stream dangling here, it will either be dropped by the default "complete" action
+ * of the stream, or by our packet callback, or when the manager is shut down. */
+
+ return 0;
+}
+
+int manager_dns_stub_tcp_fd(Manager *m) {
+ static const int one = 1;
+
+ union sockaddr_union sa = {
+ .in.sin_family = AF_INET,
+ .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB),
+ .in.sin_port = htobe16(53),
+ };
+
+ int r;
+
+ if (m->dns_stub_tcp_fd >= 0)
+ return m->dns_stub_tcp_fd;
+
+ m->dns_stub_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->dns_stub_tcp_fd < 0)
+ return -errno;
+
+ r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->dns_stub_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ /* Make sure no traffic from outside the local host can leak to onto this socket */
+ r = setsockopt(m->dns_stub_tcp_fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3);
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = bind(m->dns_stub_tcp_fd, &sa.sa, sizeof(sa.in));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = listen(m->dns_stub_tcp_fd, SOMAXCONN);
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = sd_event_add_io(m->event, &m->dns_stub_tcp_event_source, m->dns_stub_tcp_fd, EPOLLIN, on_dns_stub_stream, m);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(m->dns_stub_tcp_event_source, "dns-stub-tcp");
+
+ return m->dns_stub_tcp_fd;
+
+fail:
+ m->dns_stub_tcp_fd = safe_close(m->dns_stub_tcp_fd);
+ return r;
+}
+
+int manager_dns_stub_start(Manager *m) {
+ int r;
+
+ assert(m);
+
+ r = manager_dns_stub_udp_fd(m);
+ if (r == -EADDRINUSE)
+ goto eaddrinuse;
+ if (r < 0)
+ return r;
+
+ r = manager_dns_stub_tcp_fd(m);
+ if (r == -EADDRINUSE)
+ goto eaddrinuse;
+ if (r < 0)
+ return r;
+
+ return 0;
+
+eaddrinuse:
+ log_warning("Another process is already listening on 127.0.0.53:53. Turning off local DNS stub support.");
+ manager_dns_stub_stop(m);
+
+ return 0;
+}
+
+void manager_dns_stub_stop(Manager *m) {
+ assert(m);
+
+ m->dns_stub_udp_event_source = sd_event_source_unref(m->dns_stub_udp_event_source);
+ m->dns_stub_tcp_event_source = sd_event_source_unref(m->dns_stub_tcp_event_source);
+
+ m->dns_stub_udp_fd = safe_close(m->dns_stub_udp_fd);
+ m->dns_stub_tcp_fd = safe_close(m->dns_stub_tcp_fd);
+}
diff --git a/src/resolve/resolved-dns-stub.h b/src/resolve/resolved-dns-stub.h
new file mode 100644
index 0000000000..fce4d25ede
--- /dev/null
+++ b/src/resolve/resolved-dns-stub.h
@@ -0,0 +1,31 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "resolved-manager.h"
+
+/* 127.0.0.53 in native endian */
+#define INADDR_DNS_STUB ((in_addr_t) 0x7f000035U)
+
+int manager_dns_stub_udp_fd(Manager *m);
+int manager_dns_stub_tcp_fd(Manager *m);
+
+void manager_dns_stub_stop(Manager *m);
+int manager_dns_stub_start(Manager *m);
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index a4a67623e7..d455b6b1fa 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -60,7 +60,14 @@ static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) {
static void dns_transaction_close_connection(DnsTransaction *t) {
assert(t);
- t->stream = dns_stream_free(t->stream);
+ if (t->stream) {
+ /* Let's detach the stream from our transaction, in case something else keeps a reference to it. */
+ t->stream->complete = NULL;
+ t->stream->on_packet = NULL;
+ t->stream->transaction = NULL;
+ t->stream = dns_stream_unref(t->stream);
+ }
+
t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source);
t->dns_udp_fd = safe_close(t->dns_udp_fd);
}
@@ -207,6 +214,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
t->answer_nsec_ttl = (uint32_t) -1;
t->key = dns_resource_key_ref(key);
t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
+ t->clamp_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
t->id = pick_new_id(s->manager);
@@ -371,22 +379,38 @@ static int dns_transaction_pick_server(DnsTransaction *t) {
assert(t);
assert(t->scope->protocol == DNS_PROTOCOL_DNS);
+ /* Pick a DNS server and a feature level for it. */
+
server = dns_scope_get_dns_server(t->scope);
if (!server)
return -ESRCH;
+ /* If we changed the server invalidate the feature level clamping, as the new server might have completely
+ * different properties. */
+ if (server != t->server)
+ t->clamp_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
+
t->current_feature_level = dns_server_possible_feature_level(server);
+ /* Clamp the feature level if that is requested. */
+ if (t->clamp_feature_level != _DNS_SERVER_FEATURE_LEVEL_INVALID &&
+ t->current_feature_level > t->clamp_feature_level)
+ t->current_feature_level = t->clamp_feature_level;
+
+ log_debug("Using feature level %s for transaction %u.", dns_server_feature_level_to_string(t->current_feature_level), t->id);
+
if (server == t->server)
return 0;
dns_server_unref(t->server);
t->server = dns_server_ref(server);
+ log_debug("Using DNS server %s for transaction %u.", dns_server_string(t->server), t->id);
+
return 1;
}
-static void dns_transaction_retry(DnsTransaction *t) {
+static void dns_transaction_retry(DnsTransaction *t, bool next_server) {
int r;
assert(t);
@@ -394,7 +418,8 @@ static void dns_transaction_retry(DnsTransaction *t) {
log_debug("Retrying transaction %" PRIu16 ".", t->id);
/* Before we try again, switch to a new server. */
- dns_scope_next_dns_server(t->scope);
+ if (next_server)
+ dns_scope_next_dns_server(t->scope);
r = dns_transaction_go(t);
if (r < 0) {
@@ -404,8 +429,12 @@ static void dns_transaction_retry(DnsTransaction *t) {
}
static int dns_transaction_maybe_restart(DnsTransaction *t) {
+ int r;
+
assert(t);
+ /* Returns > 0 if the transaction was restarted, 0 if not */
+
if (!t->server)
return 0;
@@ -420,7 +449,12 @@ static int dns_transaction_maybe_restart(DnsTransaction *t) {
log_debug("Server feature level is now lower than when we began our transaction. Restarting with new ID.");
dns_transaction_shuffle_id(t);
- return dns_transaction_go(t);
+
+ r = dns_transaction_go(t);
+ if (r < 0)
+ return r;
+
+ return 1;
}
static int on_stream_complete(DnsStream *s, int error) {
@@ -435,7 +469,7 @@ static int on_stream_complete(DnsStream *s, int error) {
t = s->transaction;
p = dns_packet_ref(s->read_packet);
- t->stream = dns_stream_free(t->stream);
+ dns_transaction_close_connection(t);
if (ERRNO_IS_DISCONNECT(error)) {
usec_t usec;
@@ -451,7 +485,7 @@ static int on_stream_complete(DnsStream *s, int error) {
assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0);
dns_server_packet_lost(t->server, IPPROTO_TCP, t->current_feature_level, usec - t->start_usec);
- dns_transaction_retry(t);
+ dns_transaction_retry(t, true);
return 0;
}
if (error != 0) {
@@ -547,7 +581,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
r = dns_stream_write_packet(t->stream, t->sent);
if (r < 0) {
- t->stream = dns_stream_free(t->stream);
+ t->stream = dns_stream_unref(t->stream);
return r;
}
@@ -557,8 +591,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
/* The interface index is difficult to determine if we are
* connecting to the local host, hence fill this in right away
* instead of determining it from the socket */
- if (t->scope->link)
- t->stream->ifindex = t->scope->link->ifindex;
+ t->stream->ifindex = dns_scope_ifindex(t->scope);
dns_transaction_reset_answer(t);
@@ -575,6 +608,10 @@ static void dns_transaction_cache_answer(DnsTransaction *t) {
if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR))
return;
+ /* Caching disabled? */
+ if (!t->scope->manager->enable_cache)
+ return;
+
/* We never cache if this packet is from the local host, under
* the assumption that a locally running DNS server would
* cache this anyway, and probably knows better when to flush
@@ -626,14 +663,15 @@ static int dns_transaction_dnssec_ready(DnsTransaction *t) {
return 0;
case DNS_TRANSACTION_RCODE_FAILURE:
- if (dt->answer_rcode != DNS_RCODE_NXDOMAIN) {
+ if (!IN_SET(dt->answer_rcode, DNS_RCODE_NXDOMAIN, DNS_RCODE_SERVFAIL)) {
log_debug("Auxiliary DNSSEC RR query failed with rcode=%s.", dns_rcode_to_string(dt->answer_rcode));
goto fail;
}
- /* Fall-through: NXDOMAIN is good enough for us. This is because some DNS servers erronously
- * return NXDOMAIN for empty non-terminals (Akamai...), and we need to handle that nicely, when
- * asking for parent SOA or similar RRs to make unsigned proofs. */
+ /* Fall-through: NXDOMAIN/SERVFAIL is good enough for us. This is because some DNS servers
+ * erronously return NXDOMAIN/SERVFAIL for empty non-terminals (Akamai...) or missing DS
+ * records (Facebook), and we need to handle that nicely, when asking for parent SOA or similar
+ * RRs to make unsigned proofs. */
case DNS_TRANSACTION_SUCCESS:
/* All good. */
@@ -798,12 +836,9 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
switch (t->scope->protocol) {
case DNS_PROTOCOL_LLMNR:
- assert(t->scope->link);
+ /* For LLMNR we will not accept any packets from other interfaces */
- /* For LLMNR we will not accept any packets from other
- * interfaces */
-
- if (p->ifindex != t->scope->link->ifindex)
+ if (p->ifindex != dns_scope_ifindex(t->scope))
return;
if (p->family != t->scope->family)
@@ -820,10 +855,9 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
break;
case DNS_PROTOCOL_MDNS:
- assert(t->scope->link);
-
/* For mDNS we will not accept any packets from other interfaces */
- if (p->ifindex != t->scope->link->ifindex)
+
+ if (p->ifindex != dns_scope_ifindex(t->scope))
return;
if (p->family != t->scope->family)
@@ -874,10 +908,22 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
/* Request failed, immediately try again with reduced features */
- log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p)));
- dns_server_packet_failed(t->server, t->current_feature_level);
- dns_transaction_retry(t);
+ if (t->current_feature_level <= DNS_SERVER_FEATURE_LEVEL_WORST) {
+ /* This was already at the lowest possible feature level? If so, we can't downgrade
+ * this transaction anymore, hence let's process the response, and accept the rcode. */
+ log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p)));
+ break;
+ }
+
+ /* Reduce this feature level by one and try again. */
+ t->clamp_feature_level = t->current_feature_level - 1;
+
+ log_debug("Server returned error %s, retrying transaction with reduced feature level %s.",
+ dns_rcode_to_string(DNS_PACKET_RCODE(p)),
+ dns_server_feature_level_to_string(t->clamp_feature_level));
+
+ dns_transaction_retry(t, false /* use the same server */);
return;
} else if (DNS_PACKET_TC(p))
dns_server_packet_truncated(t->server, t->current_feature_level);
@@ -922,7 +968,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
goto fail;
/* On DNS, couldn't send? Try immediately again, with a new server */
- dns_transaction_retry(t);
+ dns_transaction_retry(t, true);
}
return;
@@ -935,11 +981,19 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
return;
}
- /* Report that the OPT RR was missing */
if (t->server) {
+ /* Report that we successfully received a valid packet with a good rcode after we initially got a bad
+ * rcode and subsequently downgraded the protocol */
+
+ if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN) &&
+ t->clamp_feature_level != _DNS_SERVER_FEATURE_LEVEL_INVALID)
+ dns_server_packet_rcode_downgrade(t->server, t->clamp_feature_level);
+
+ /* Report that the OPT RR was missing */
if (!p->opt)
dns_server_packet_bad_opt(t->server, t->current_feature_level);
+ /* Report that we successfully received a packet */
dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size);
}
@@ -1026,7 +1080,7 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use
assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0);
dns_server_packet_lost(t->server, IPPROTO_UDP, t->current_feature_level, usec - t->start_usec);
- dns_transaction_retry(t);
+ dns_transaction_retry(t, true);
return 0;
}
if (r < 0) {
@@ -1135,7 +1189,7 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
log_debug("Timeout reached on transaction %" PRIu16 ".", t->id);
- dns_transaction_retry(t);
+ dns_transaction_retry(t, true);
return 0;
}
@@ -1246,7 +1300,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
* for probing or verifying a zone item. */
if (set_isempty(t->notify_zone_items)) {
- r = dns_zone_lookup(&t->scope->zone, t->key, &t->answer, NULL, NULL);
+ r = dns_zone_lookup(&t->scope->zone, t->key, dns_scope_ifindex(t->scope), &t->answer, NULL, NULL);
if (r < 0)
return r;
if (r > 0) {
@@ -1270,7 +1324,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
/* Let's then prune all outdated entries */
dns_cache_prune(&t->scope->cache);
- r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer, &t->answer_authenticated);
+ r = dns_cache_lookup(&t->scope->cache, t->key, t->clamp_ttl, &t->answer_rcode, &t->answer, &t->answer_authenticated);
if (r < 0)
return r;
if (r > 0) {
@@ -1426,6 +1480,9 @@ int dns_transaction_go(DnsTransaction *t) {
assert(t);
+ /* Returns > 0 if the transaction is now pending, returns 0 if could be processed immediately and has finished
+ * now. */
+
assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
r = dns_transaction_prepare(t, ts);
@@ -1763,8 +1820,10 @@ static bool dns_transaction_dnssec_supported(DnsTransaction *t) {
if (!t->server)
return true;
- if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_DO)
- return false;
+ /* Note that we do not check the feature level actually used for the transaction but instead the feature level
+ * the server is known to support currently, as the transaction feature level might be lower than what the
+ * server actually supports, since we might have downgraded this transaction's feature level because we got a
+ * SERVFAIL earlier and wanted to check whether downgrading fixes it. */
return dns_server_dnssec_supported(t->server);
}
@@ -2856,7 +2915,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (!dns_transaction_dnssec_supported_full(t)) {
/* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */
t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
- log_debug("Not validating response for %" PRIu16 ", server lacks DNSSEC support.", t->id);
+ log_debug("Not validating response for %" PRIu16 ", used server feature level does not support DNSSEC.", t->id);
return 0;
}
diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h
index eaece91533..96b066845d 100644
--- a/src/resolve/resolved-dns-transaction.h
+++ b/src/resolve/resolved-dns-transaction.h
@@ -74,6 +74,8 @@ struct DnsTransaction {
bool initial_jitter_scheduled:1;
bool initial_jitter_elapsed:1;
+ bool clamp_ttl:1;
+
DnsPacket *sent, *received;
DnsAnswer *answer;
@@ -115,6 +117,9 @@ struct DnsTransaction {
/* The features of the DNS server at time of transaction start */
DnsServerFeatureLevel current_feature_level;
+ /* If we got SERVFAIL back, we retry the lookup, using a lower feature level than we used before. */
+ DnsServerFeatureLevel clamp_feature_level;
+
/* Query candidates this transaction is referenced by and that
* shall be notified about this specific transaction
* completing. */
diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c
index 850eed8cb8..746a979f47 100644
--- a/src/resolve/resolved-dns-zone.c
+++ b/src/resolve/resolved-dns-zone.c
@@ -287,13 +287,16 @@ int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
return 0;
}
-int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
+int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
unsigned n_answer = 0;
DnsZoneItem *j, *first;
bool tentative = true, need_soa = false;
int r;
+ /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
+ * ifindex field in the answer with it */
+
assert(z);
assert(key);
assert(ret_answer);
@@ -389,7 +392,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
if (k < 0)
return k;
if (k > 0) {
- r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED);
+ r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
@@ -398,7 +401,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
}
if (found && !added) {
- r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL);
+ r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
if (r < 0)
return r;
}
@@ -415,7 +418,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
if (j->state != DNS_ZONE_ITEM_PROBING)
tentative = false;
- r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED);
+ r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
if (r < 0)
return r;
}
@@ -435,7 +438,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns
}
if (add_soa) {
- r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL);
+ r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
if (r < 0)
return r;
}
diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h
index 408833c359..a41df37e6b 100644
--- a/src/resolve/resolved-dns-zone.h
+++ b/src/resolve/resolved-dns-zone.h
@@ -65,7 +65,7 @@ void dns_zone_flush(DnsZone *z);
int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe);
void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr);
-int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **answer, DnsAnswer **soa, bool *tentative);
+int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **answer, DnsAnswer **soa, bool *tentative);
void dns_zone_item_conflict(DnsZoneItem *i);
void dns_zone_item_notify(DnsZoneItem *i);
diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf
index 82f26215df..2fd56bce26 100644
--- a/src/resolve/resolved-gperf.gperf
+++ b/src/resolve/resolved-gperf.gperf
@@ -19,3 +19,4 @@ Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0
Resolve.Domains, config_parse_search_domains, 0, 0
Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support)
Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode)
+Resolve.Cache, config_parse_bool, 0, offsetof(Manager, enable_cache)
diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c
index 7f21891819..364812250f 100644
--- a/src/resolve/resolved-link-bus.c
+++ b/src/resolve/resolved-link-bus.c
@@ -18,15 +18,33 @@
***/
#include "alloc-util.h"
+#include "bus-common-errors.h"
#include "bus-util.h"
#include "parse-util.h"
#include "resolve-util.h"
#include "resolved-bus.h"
#include "resolved-link-bus.h"
+#include "resolved-resolv-conf.h"
#include "strv.h"
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_resolve_support, resolve_support, ResolveSupport);
-static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_dnssec_mode, dnssec_mode, DnssecMode);
+
+static int property_get_dnssec_mode(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Link *l = userdata;
+
+ assert(reply);
+ assert(l);
+
+ return sd_bus_message_append(reply, "s", dnssec_mode_to_string(link_get_dnssec_mode(l)));
+}
static int property_get_dns(
sd_bus *bus,
@@ -157,6 +175,17 @@ static int property_get_dnssec_supported(
return sd_bus_message_append(reply, "b", link_dnssec_supported(l));
}
+static int verify_unmanaged_link(Link *l, sd_bus_error *error) {
+ assert(l);
+
+ if (l->flags & IFF_LOOPBACK)
+ return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is loopback device.", l->name);
+ if (l->is_managed)
+ return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is managed.", l->name);
+
+ return 0;
+}
+
int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_free_ struct in_addr_data *dns = NULL;
size_t allocated = 0, n = 0;
@@ -167,6 +196,10 @@ int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_
assert(message);
assert(l);
+ r = verify_unmanaged_link(l, error);
+ if (r < 0)
+ return r;
+
r = sd_bus_message_enter_container(message, 'a', "(iay)");
if (r < 0)
return r;
@@ -197,6 +230,9 @@ int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_
if (sz != FAMILY_ADDRESS_SIZE(family))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size");
+ if (!dns_server_address_valid(family, d))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNS server address");
+
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
@@ -218,11 +254,11 @@ int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_
for (i = 0; i < n; i++) {
DnsServer *s;
- s = dns_server_find(l->dns_servers, dns[i].family, &dns[i].address);
+ s = dns_server_find(l->dns_servers, dns[i].family, &dns[i].address, 0);
if (s)
dns_server_move_back_and_unmark(s);
else {
- r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i].family, &dns[i].address);
+ r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i].family, &dns[i].address, 0);
if (r < 0)
goto clear;
}
@@ -232,6 +268,9 @@ int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_
dns_server_unlink_marked(l->dns_servers);
link_allocate_scopes(l);
+ (void) link_save_user(l);
+ (void) manager_write_resolv_conf(l->manager);
+
return sd_bus_reply_method_return(message, NULL);
clear:
@@ -246,6 +285,10 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_
assert(message);
assert(l);
+ r = verify_unmanaged_link(l, error);
+ if (r < 0)
+ return r;
+
r = sd_bus_message_enter_container(message, 'a', "(sb)");
if (r < 0)
return r;
@@ -306,6 +349,10 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_
goto clear;
dns_search_domain_unlink_marked(l->search_domains);
+
+ (void) link_save_user(l);
+ (void) manager_write_resolv_conf(l->manager);
+
return sd_bus_reply_method_return(message, NULL);
clear:
@@ -322,6 +369,10 @@ int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_er
assert(message);
assert(l);
+ r = verify_unmanaged_link(l, error);
+ if (r < 0)
+ return r;
+
r = sd_bus_message_read(message, "s", &llmnr);
if (r < 0)
return r;
@@ -338,6 +389,8 @@ int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_er
link_allocate_scopes(l);
link_add_rrs(l, false);
+ (void) link_save_user(l);
+
return sd_bus_reply_method_return(message, NULL);
}
@@ -350,6 +403,10 @@ int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_err
assert(message);
assert(l);
+ r = verify_unmanaged_link(l, error);
+ if (r < 0)
+ return r;
+
r = sd_bus_message_read(message, "s", &mdns);
if (r < 0)
return r;
@@ -366,6 +423,8 @@ int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_err
link_allocate_scopes(l);
link_add_rrs(l, false);
+ (void) link_save_user(l);
+
return sd_bus_reply_method_return(message, NULL);
}
@@ -378,6 +437,10 @@ int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_e
assert(message);
assert(l);
+ r = verify_unmanaged_link(l, error);
+ if (r < 0)
+ return r;
+
r = sd_bus_message_read(message, "s", &dnssec);
if (r < 0)
return r;
@@ -392,6 +455,8 @@ int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_e
link_set_dnssec_mode(l, mode);
+ (void) link_save_user(l);
+
return sd_bus_reply_method_return(message, NULL);
}
@@ -405,6 +470,10 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v
assert(message);
assert(l);
+ r = verify_unmanaged_link(l, error);
+ if (r < 0)
+ return r;
+
r = sd_bus_message_read_strv(message, &ntas);
if (r < 0)
return r;
@@ -431,19 +500,29 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v
l->dnssec_negative_trust_anchors = ns;
ns = NULL;
+ (void) link_save_user(l);
+
return sd_bus_reply_method_return(message, NULL);
}
int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Link *l = userdata;
+ int r;
assert(message);
assert(l);
+ r = verify_unmanaged_link(l, error);
+ if (r < 0)
+ return r;
+
link_flush_settings(l);
link_allocate_scopes(l);
link_add_rrs(l, false);
+ (void) link_save_user(l);
+ (void) manager_write_resolv_conf(l->manager);
+
return sd_bus_reply_method_return(message, NULL);
}
@@ -455,7 +534,7 @@ const sd_bus_vtable link_vtable[] = {
SD_BUS_PROPERTY("Domains", "a(sb)", property_get_domains, 0, 0),
SD_BUS_PROPERTY("LLMNR", "s", property_get_resolve_support, offsetof(Link, llmnr_support), 0),
SD_BUS_PROPERTY("MulticastDNS", "s", property_get_resolve_support, offsetof(Link, mdns_support), 0),
- SD_BUS_PROPERTY("DNSSEC", "s", property_get_dnssec_mode, offsetof(Link, dnssec_mode), 0),
+ SD_BUS_PROPERTY("DNSSEC", "s", property_get_dnssec_mode, 0, 0),
SD_BUS_PROPERTY("DNSSECNegativeTrustAnchors", "as", property_get_ntas, 0, 0),
SD_BUS_PROPERTY("DNSSECSupported", "b", property_get_dnssec_supported, 0, 0),
diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c
index b0dc65036d..ea4a007139 100644
--- a/src/resolve/resolved-link.c
+++ b/src/resolve/resolved-link.c
@@ -22,7 +22,10 @@
#include "sd-network.h"
#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
#include "missing.h"
+#include "mkdir.h"
#include "parse-util.h"
#include "resolved-link.h"
#include "string-util.h"
@@ -49,6 +52,9 @@ int link_new(Manager *m, Link **ret, int ifindex) {
l->dnssec_mode = _DNSSEC_MODE_INVALID;
l->operstate = IF_OPER_UNKNOWN;
+ if (asprintf(&l->state_file, "/run/systemd/resolve/netif/%i", ifindex) < 0)
+ return -ENOMEM;
+
r = hashmap_put(m->links, INT_TO_PTR(ifindex), l);
if (r < 0)
return r;
@@ -93,6 +99,8 @@ Link *link_free(Link *l) {
dns_scope_free(l->mdns_ipv4_scope);
dns_scope_free(l->mdns_ipv6_scope);
+ free(l->state_file);
+
free(l);
return NULL;
}
@@ -165,7 +173,7 @@ void link_add_rrs(Link *l, bool force_remove) {
link_address_add_rrs(a, force_remove);
}
-int link_update_rtnl(Link *l, sd_netlink_message *m) {
+int link_process_rtnl(Link *l, sd_netlink_message *m) {
const char *n = NULL;
int r;
@@ -190,6 +198,27 @@ int link_update_rtnl(Link *l, sd_netlink_message *m) {
return 0;
}
+static int link_update_dns_server_one(Link *l, const char *name) {
+ union in_addr_union a;
+ DnsServer *s;
+ int family, r;
+
+ assert(l);
+ assert(name);
+
+ r = in_addr_from_string_auto(name, &family, &a);
+ if (r < 0)
+ return r;
+
+ s = dns_server_find(l->dns_servers, family, &a, 0);
+ if (s) {
+ dns_server_move_back_and_unmark(s);
+ return 0;
+ }
+
+ return dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a, 0);
+}
+
static int link_update_dns_servers(Link *l) {
_cleanup_strv_free_ char **nameservers = NULL;
char **nameserver;
@@ -208,22 +237,9 @@ static int link_update_dns_servers(Link *l) {
dns_server_mark_all(l->dns_servers);
STRV_FOREACH(nameserver, nameservers) {
- union in_addr_union a;
- DnsServer *s;
- int family;
-
- r = in_addr_from_string_auto(*nameserver, &family, &a);
+ r = link_update_dns_server_one(l, *nameserver);
if (r < 0)
goto clear;
-
- s = dns_server_find(l->dns_servers, family, &a);
- if (s)
- dns_server_move_back_and_unmark(s);
- else {
- r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a);
- if (r < 0)
- goto clear;
- }
}
dns_server_unlink_marked(l->dns_servers);
@@ -341,7 +357,6 @@ clear:
static int link_update_dnssec_negative_trust_anchors(Link *l) {
_cleanup_strv_free_ char **ntas = NULL;
_cleanup_set_free_free_ Set *ns = NULL;
- char **i;
int r;
assert(l);
@@ -358,11 +373,9 @@ static int link_update_dnssec_negative_trust_anchors(Link *l) {
if (!ns)
return -ENOMEM;
- STRV_FOREACH(i, ntas) {
- r = set_put_strdup(ns, *i);
- if (r < 0)
- return r;
- }
+ r = set_put_strdupv(ns, ntas);
+ if (r < 0)
+ return r;
set_free_free(l->dnssec_negative_trust_anchors);
l->dnssec_negative_trust_anchors = ns;
@@ -379,6 +392,9 @@ static int link_update_search_domain_one(Link *l, const char *name, bool route_o
DnsSearchDomain *d;
int r;
+ assert(l);
+ assert(name);
+
r = dns_search_domain_find(l->search_domains, name, &d);
if (r < 0)
return r;
@@ -439,7 +455,7 @@ clear:
return r;
}
-static int link_is_unmanaged(Link *l) {
+static int link_is_managed(Link *l) {
_cleanup_free_ char *state = NULL;
int r;
@@ -447,11 +463,11 @@ static int link_is_unmanaged(Link *l) {
r = sd_network_link_get_setup_state(l->ifindex, &state);
if (r == -ENODATA)
- return 1;
+ return 0;
if (r < 0)
return r;
- return STR_IN_SET(state, "pending", "unmanaged");
+ return !STR_IN_SET(state, "pending", "unmanaged");
}
static void link_read_settings(Link *l) {
@@ -461,12 +477,12 @@ static void link_read_settings(Link *l) {
/* Read settings from networkd, except when networkd is not managing this interface. */
- r = link_is_unmanaged(l);
+ r = link_is_managed(l);
if (r < 0) {
log_warning_errno(r, "Failed to determine whether interface %s is managed: %m", l->name);
return;
}
- if (r > 0) {
+ if (r == 0) {
/* If this link used to be managed, but is now unmanaged, flush all our settings — but only once. */
if (l->is_managed)
@@ -503,10 +519,11 @@ static void link_read_settings(Link *l) {
log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name);
}
-int link_update_monitor(Link *l) {
+int link_update(Link *l) {
assert(l);
link_read_settings(l);
+ link_load_user(l);
link_allocate_scopes(l);
link_add_rrs(l, false);
@@ -838,3 +855,261 @@ bool link_address_relevant(LinkAddress *a, bool local_multicast) {
return true;
}
+
+static bool link_needs_save(Link *l) {
+ assert(l);
+
+ /* Returns true if any of the settings where set different from the default */
+
+ if (l->is_managed)
+ return false;
+
+ if (l->llmnr_support != RESOLVE_SUPPORT_YES ||
+ l->mdns_support != RESOLVE_SUPPORT_NO ||
+ l->dnssec_mode != _DNSSEC_MODE_INVALID)
+ return true;
+
+ if (l->dns_servers ||
+ l->search_domains)
+ return true;
+
+ if (!set_isempty(l->dnssec_negative_trust_anchors))
+ return true;
+
+ return false;
+}
+
+int link_save_user(Link *l) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *v;
+ int r;
+
+ assert(l);
+ assert(l->state_file);
+
+ if (!link_needs_save(l)) {
+ (void) unlink(l->state_file);
+ return 0;
+ }
+
+ r = mkdir_parents(l->state_file, 0700);
+ if (r < 0)
+ goto fail;
+
+ r = fopen_temporary(l->state_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ fputs("# This is private data. Do not parse.\n", f);
+
+ v = resolve_support_to_string(l->llmnr_support);
+ if (v)
+ fprintf(f, "LLMNR=%s\n", v);
+
+ v = resolve_support_to_string(l->mdns_support);
+ if (v)
+ fprintf(f, "MDNS=%s\n", v);
+
+ v = dnssec_mode_to_string(l->dnssec_mode);
+ if (v)
+ fprintf(f, "DNSSEC=%s\n", v);
+
+ if (l->dns_servers) {
+ DnsServer *server;
+
+ fputs("SERVERS=", f);
+ LIST_FOREACH(servers, server, l->dns_servers) {
+
+ if (server != l->dns_servers)
+ fputc(' ', f);
+
+ v = dns_server_string(server);
+ if (!v) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ fputs(v, f);
+ }
+ fputc('\n', f);
+ }
+
+ if (l->search_domains) {
+ DnsSearchDomain *domain;
+
+ fputs("DOMAINS=", f);
+ LIST_FOREACH(domains, domain, l->search_domains) {
+
+ if (domain != l->search_domains)
+ fputc(' ', f);
+
+ if (domain->route_only)
+ fputc('~', f);
+
+ fputs(DNS_SEARCH_DOMAIN_NAME(domain), f);
+ }
+ fputc('\n', f);
+ }
+
+ if (!set_isempty(l->dnssec_negative_trust_anchors)) {
+ bool space = false;
+ Iterator i;
+ char *nta;
+
+ fputs("NTAS=", f);
+ SET_FOREACH(nta, l->dnssec_negative_trust_anchors, i) {
+
+ if (space)
+ fputc(' ', f);
+
+ fputs(nta, f);
+ space = true;
+ }
+ fputc('\n', f);
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, l->state_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ (void) unlink(l->state_file);
+
+ if (temp_path)
+ (void) unlink(temp_path);
+
+ return log_error_errno(r, "Failed to save link data %s: %m", l->state_file);
+}
+
+int link_load_user(Link *l) {
+ _cleanup_free_ char
+ *llmnr = NULL,
+ *mdns = NULL,
+ *dnssec = NULL,
+ *servers = NULL,
+ *domains = NULL,
+ *ntas = NULL;
+
+ ResolveSupport s;
+ int r;
+
+ assert(l);
+ assert(l->state_file);
+
+ /* Try to load only a single time */
+ if (l->loaded)
+ return 0;
+ l->loaded = true;
+
+ if (l->is_managed)
+ return 0; /* if the device is managed, then networkd is our configuration source, not the bus API */
+
+ r = parse_env_file(l->state_file, NEWLINE,
+ "LLMNR", &llmnr,
+ "MDNS", &mdns,
+ "DNSSEC", &dnssec,
+ "SERVERS", &servers,
+ "DOMAINS", &domains,
+ "NTAS", &ntas,
+ NULL);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ goto fail;
+
+ link_flush_settings(l);
+
+ /* If we can't recognize the LLMNR or MDNS setting we don't override the default */
+ s = resolve_support_from_string(llmnr);
+ if (s >= 0)
+ l->llmnr_support = s;
+
+ s = resolve_support_from_string(mdns);
+ if (s >= 0)
+ l->mdns_support = s;
+
+ /* If we can't recognize the DNSSEC setting, then set it to invalid, so that the daemon default is used. */
+ l->dnssec_mode = dnssec_mode_from_string(dnssec);
+
+ if (servers) {
+ const char *p = servers;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&p, &word, NULL, 0);
+ if (r < 0)
+ goto fail;
+ if (r == 0)
+ break;
+
+ r = link_update_dns_server_one(l, word);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to load DNS server '%s', ignoring: %m", word);
+ continue;
+ }
+ }
+ }
+
+ if (domains) {
+ const char *p = domains;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+ const char *n;
+ bool is_route;
+
+ r = extract_first_word(&p, &word, NULL, 0);
+ if (r < 0)
+ goto fail;
+ if (r == 0)
+ break;
+
+ is_route = word[0] == '~';
+ n = is_route ? word + 1 : word;
+
+ r = link_update_search_domain_one(l, n, is_route);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to load search domain '%s', ignoring: %m", word);
+ continue;
+ }
+ }
+ }
+
+ if (ntas) {
+ _cleanup_set_free_free_ Set *ns = NULL;
+
+ ns = set_new(&dns_name_hash_ops);
+ if (!ns) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ r = set_put_strsplit(ns, ntas, NULL, 0);
+ if (r < 0)
+ goto fail;
+
+ l->dnssec_negative_trust_anchors = ns;
+ ns = NULL;
+ }
+
+ return 0;
+
+fail:
+ return log_error_errno(r, "Failed to load link data %s: %m", l->state_file);
+}
+
+void link_remove_user(Link *l) {
+ assert(l);
+ assert(l->state_file);
+
+ (void) unlink(l->state_file);
+}
diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h
index f534c12824..6a2343f9f7 100644
--- a/src/resolve/resolved-link.h
+++ b/src/resolve/resolved-link.h
@@ -81,12 +81,15 @@ struct Link {
char name[IF_NAMESIZE];
uint32_t mtu;
uint8_t operstate;
+
+ bool loaded;
+ char *state_file;
};
int link_new(Manager *m, Link **ret, int ifindex);
Link *link_free(Link *l);
-int link_update_rtnl(Link *l, sd_netlink_message *m);
-int link_update_monitor(Link *l);
+int link_process_rtnl(Link *l, sd_netlink_message *m);
+int link_update(Link *l);
bool link_relevant(Link *l, int family, bool local_multicast);
LinkAddress* link_find_address(Link *l, int family, const union in_addr_union *in_addr);
void link_add_rrs(Link *l, bool force_remove);
@@ -102,6 +105,10 @@ void link_next_dns_server(Link *l);
DnssecMode link_get_dnssec_mode(Link *l);
bool link_dnssec_supported(Link *l);
+int link_save_user(Link *l);
+int link_load_user(Link *l);
+void link_remove_user(Link *l);
+
int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr);
LinkAddress *link_address_free(LinkAddress *a);
int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m);
diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c
index 8b1d71a3eb..3516af58ee 100644
--- a/src/resolve/resolved-llmnr.c
+++ b/src/resolve/resolved-llmnr.c
@@ -91,18 +91,19 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u
DnsScope *scope;
int r;
+ assert(s);
+ assert(fd >= 0);
+ assert(m);
+
r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p);
if (r <= 0)
return r;
scope = manager_find_scope(m, p);
- if (!scope) {
+ if (!scope)
log_warning("Got LLMNR UDP packet on unknown scope. Ignoring.");
- return 0;
- }
-
- if (dns_packet_validate_reply(p) > 0) {
- log_debug("Got LLMNR reply packet for id %u", DNS_PACKET_ID(p));
+ else if (dns_packet_validate_reply(p) > 0) {
+ log_debug("Got LLMNR UDP reply packet for id %u", DNS_PACKET_ID(p));
dns_scope_check_conflicts(scope, p);
@@ -111,7 +112,7 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u
dns_transaction_process_reply(t, p);
} else if (dns_packet_validate_query(p) > 0) {
- log_debug("Got LLMNR query packet for id %u", DNS_PACKET_ID(p));
+ log_debug("Got LLMNR UDP query packet for id %u", DNS_PACKET_ID(p));
dns_scope_process_query(scope, NULL, p);
} else
@@ -283,25 +284,19 @@ static int on_llmnr_stream_packet(DnsStream *s) {
DnsScope *scope;
assert(s);
+ assert(s->read_packet);
scope = manager_find_scope(s->manager, s->read_packet);
- if (!scope) {
+ if (!scope)
log_warning("Got LLMNR TCP packet on unknown scope. Ignoring.");
- return 0;
- }
-
- if (dns_packet_validate_query(s->read_packet) > 0) {
- log_debug("Got query packet for id %u", DNS_PACKET_ID(s->read_packet));
+ else if (dns_packet_validate_query(s->read_packet) > 0) {
+ log_debug("Got LLMNR TCP query packet for id %u", DNS_PACKET_ID(s->read_packet));
dns_scope_process_query(scope, s, s->read_packet);
-
- /* If no reply packet was set, we free the stream */
- if (s->write_packet)
- return 0;
} else
- log_debug("Invalid LLMNR TCP packet.");
+ log_debug("Invalid LLMNR TCP packet, ignoring.");
- dns_stream_free(s);
+ dns_stream_unref(s);
return 0;
}
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index 9600bde1e9..92ade820ac 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -23,6 +23,7 @@
#include "af-list.h"
#include "alloc-util.h"
+#include "dirent-util.h"
#include "dns-domain.h"
#include "fd-util.h"
#include "fileio-label.h"
@@ -35,6 +36,7 @@
#include "random-util.h"
#include "resolved-bus.h"
#include "resolved-conf.h"
+#include "resolved-dns-stub.h"
#include "resolved-etc-hosts.h"
#include "resolved-llmnr.h"
#include "resolved-manager.h"
@@ -78,11 +80,11 @@ static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *
goto fail;
}
- r = link_update_rtnl(l, mm);
+ r = link_process_rtnl(l, mm);
if (r < 0)
goto fail;
- r = link_update_monitor(l);
+ r = link_update(l);
if (r < 0)
goto fail;
@@ -95,6 +97,7 @@ static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *
case RTM_DELLINK:
if (l) {
log_debug("Removing link %i/%s", l->ifindex, l->name);
+ link_remove_user(l);
link_free(l);
}
@@ -279,14 +282,12 @@ static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *
sd_network_monitor_flush(m->network_monitor);
HASHMAP_FOREACH(l, m->links, i) {
- r = link_update_monitor(l);
+ r = link_update(l);
if (r < 0)
log_warning_errno(r, "Failed to update monitor information for %i: %m", l->ifindex);
}
- r = manager_write_resolv_conf(m);
- if (r < 0)
- log_warning_errno(r, "Could not update "PRIVATE_RESOLV_CONF": %m");
+ (void) manager_write_resolv_conf(m);
return 0;
}
@@ -468,6 +469,18 @@ static int manager_sigusr1(sd_event_source *s, const struct signalfd_siginfo *si
return 0;
}
+static int manager_sigusr2(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
+ Manager *m = userdata;
+
+ assert(s);
+ assert(si);
+ assert(m);
+
+ manager_flush_caches(m);
+
+ return 0;
+}
+
int manager_new(Manager **ret) {
_cleanup_(manager_freep) Manager *m = NULL;
int r;
@@ -481,11 +494,13 @@ int manager_new(Manager **ret) {
m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1;
m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1;
m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1;
+ m->dns_stub_udp_fd = m->dns_stub_tcp_fd = -1;
m->hostname_fd = -1;
m->llmnr_support = RESOLVE_SUPPORT_YES;
m->mdns_support = RESOLVE_SUPPORT_NO;
m->dnssec_mode = DEFAULT_DNSSEC_MODE;
+ m->enable_cache = true;
m->read_resolv_conf = true;
m->need_builtin_fallbacks = true;
m->etc_hosts_last = m->etc_hosts_mtime = USEC_INFINITY;
@@ -528,6 +543,9 @@ int manager_new(Manager **ret) {
return r;
(void) sd_event_add_signal(m->event, &m->sigusr1_event_source, SIGUSR1, manager_sigusr1, m);
+ (void) sd_event_add_signal(m->event, &m->sigusr2_event_source, SIGUSR2, manager_sigusr2, m);
+
+ manager_cleanup_saved_user(m);
*ret = m;
m = NULL;
@@ -540,6 +558,10 @@ int manager_start(Manager *m) {
assert(m);
+ r = manager_dns_stub_start(m);
+ if (r < 0)
+ return r;
+
r = manager_llmnr_start(m);
if (r < 0)
return r;
@@ -569,6 +591,11 @@ Manager *manager_free(Manager *m) {
dns_scope_free(m->unicast_scope);
+ /* At this point only orphaned streams should remain. All others should have been freed already by their
+ * owners */
+ while (m->dns_streams)
+ dns_stream_unref(m->dns_streams);
+
hashmap_free(m->links);
hashmap_free(m->dns_transactions);
@@ -580,12 +607,14 @@ Manager *manager_free(Manager *m) {
manager_llmnr_stop(m);
manager_mdns_stop(m);
+ manager_dns_stub_stop(m);
sd_bus_slot_unref(m->prepare_for_sleep_slot);
sd_event_source_unref(m->bus_retry_event_source);
sd_bus_unref(m->bus);
sd_event_source_unref(m->sigusr1_event_source);
+ sd_event_source_unref(m->sigusr2_event_source);
sd_event_unref(m->event);
@@ -793,7 +822,14 @@ int manager_write(Manager *m, int fd, DnsPacket *p) {
return 0;
}
-static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) {
+static int manager_ipv4_send(
+ Manager *m,
+ int fd,
+ int ifindex,
+ const struct in_addr *destination,
+ uint16_t port,
+ const struct in_addr *source,
+ DnsPacket *p) {
union sockaddr_union sa = {
.in.sin_family = AF_INET,
};
@@ -806,14 +842,14 @@ static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_ad
assert(m);
assert(fd >= 0);
- assert(addr);
+ assert(destination);
assert(port > 0);
assert(p);
iov.iov_base = DNS_PACKET_DATA(p);
iov.iov_len = p->size;
- sa.in.sin_addr = *addr;
+ sa.in.sin_addr = *destination;
sa.in.sin_port = htobe16(port),
mh.msg_iov = &iov;
@@ -837,12 +873,23 @@ static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_ad
pi = (struct in_pktinfo*) CMSG_DATA(cmsg);
pi->ipi_ifindex = ifindex;
+
+ if (source)
+ pi->ipi_spec_dst = *source;
}
return sendmsg_loop(fd, &mh, 0);
}
-static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_addr *addr, uint16_t port, DnsPacket *p) {
+static int manager_ipv6_send(
+ Manager *m,
+ int fd,
+ int ifindex,
+ const struct in6_addr *destination,
+ uint16_t port,
+ const struct in6_addr *source,
+ DnsPacket *p) {
+
union sockaddr_union sa = {
.in6.sin6_family = AF_INET6,
};
@@ -855,14 +902,14 @@ static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_a
assert(m);
assert(fd >= 0);
- assert(addr);
+ assert(destination);
assert(port > 0);
assert(p);
iov.iov_base = DNS_PACKET_DATA(p);
iov.iov_len = p->size;
- sa.in6.sin6_addr = *addr;
+ sa.in6.sin6_addr = *destination;
sa.in6.sin6_port = htobe16(port),
sa.in6.sin6_scope_id = ifindex;
@@ -887,24 +934,36 @@ static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_a
pi = (struct in6_pktinfo*) CMSG_DATA(cmsg);
pi->ipi6_ifindex = ifindex;
+
+ if (source)
+ pi->ipi6_addr = *source;
}
return sendmsg_loop(fd, &mh, 0);
}
-int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p) {
+int manager_send(
+ Manager *m,
+ int fd,
+ int ifindex,
+ int family,
+ const union in_addr_union *destination,
+ uint16_t port,
+ const union in_addr_union *source,
+ DnsPacket *p) {
+
assert(m);
assert(fd >= 0);
- assert(addr);
+ assert(destination);
assert(port > 0);
assert(p);
log_debug("Sending %s packet with id %" PRIu16 " on interface %i/%s.", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family));
if (family == AF_INET)
- return manager_ipv4_send(m, fd, ifindex, &addr->in, port, p);
- else if (family == AF_INET6)
- return manager_ipv6_send(m, fd, ifindex, &addr->in6, port, p);
+ return manager_ipv4_send(m, fd, ifindex, &destination->in, port, &source->in, p);
+ if (family == AF_INET6)
+ return manager_ipv6_send(m, fd, ifindex, &destination->in6, port, &source->in6, p);
return -EAFNOSUPPORT;
}
@@ -1141,7 +1200,12 @@ int manager_compile_dns_servers(Manager *m, OrderedSet **dns) {
return 0;
}
-int manager_compile_search_domains(Manager *m, OrderedSet **domains) {
+/* filter_route is a tri-state:
+ * < 0: no filtering
+ * = 0 or false: return only domains which should be used for searching
+ * > 0 or true: return only domains which are for routing only
+ */
+int manager_compile_search_domains(Manager *m, OrderedSet **domains, int filter_route) {
DnsSearchDomain *d;
Iterator i;
Link *l;
@@ -1155,6 +1219,11 @@ int manager_compile_search_domains(Manager *m, OrderedSet **domains) {
return r;
LIST_FOREACH(domains, d, m->search_domains) {
+
+ if (filter_route >= 0 &&
+ d->route_only != !!filter_route)
+ continue;
+
r = ordered_set_put(*domains, d->name);
if (r == -EEXIST)
continue;
@@ -1165,6 +1234,11 @@ int manager_compile_search_domains(Manager *m, OrderedSet **domains) {
HASHMAP_FOREACH(l, m->links, i) {
LIST_FOREACH(domains, d, l->search_domains) {
+
+ if (filter_route >= 0 &&
+ d->route_only != !!filter_route)
+ continue;
+
r = ordered_set_put(*domains, d->name);
if (r == -EEXIST)
continue;
@@ -1236,3 +1310,69 @@ bool manager_routable(Manager *m, int family) {
return false;
}
+
+void manager_flush_caches(Manager *m) {
+ DnsScope *scope;
+
+ assert(m);
+
+ LIST_FOREACH(scopes, scope, m->dns_scopes)
+ dns_cache_flush(&scope->cache);
+
+ log_info("Flushed all caches.");
+}
+
+void manager_cleanup_saved_user(Manager *m) {
+ _cleanup_closedir_ DIR *d = NULL;
+ struct dirent *de;
+ int r;
+
+ assert(m);
+
+ /* Clean up all saved per-link files in /run/systemd/resolve/netif/ that don't have a matching interface
+ * anymore. These files are created to persist settings pushed in by the user via the bus, so that resolved can
+ * be restarted without losing this data. */
+
+ d = opendir("/run/systemd/resolve/netif/");
+ if (!d) {
+ if (errno == ENOENT)
+ return;
+
+ log_warning_errno(errno, "Failed to open interface directory: %m");
+ return;
+ }
+
+ FOREACH_DIRENT_ALL(de, d, log_error_errno(errno, "Failed to read interface directory: %m")) {
+ _cleanup_free_ char *p = NULL;
+ int ifindex;
+ Link *l;
+
+ if (!IN_SET(de->d_type, DT_UNKNOWN, DT_REG))
+ continue;
+
+ if (STR_IN_SET(de->d_name, ".", ".."))
+ continue;
+
+ r = parse_ifindex(de->d_name, &ifindex);
+ if (r < 0) /* Probably some temporary file from a previous run. Delete it */
+ goto rm;
+
+ l = hashmap_get(m->links, INT_TO_PTR(ifindex));
+ if (!l) /* link vanished */
+ goto rm;
+
+ if (l->is_managed) /* now managed by networkd, hence the bus settings are useless */
+ goto rm;
+
+ continue;
+
+ rm:
+ p = strappend("/run/systemd/resolve/netif/", de->d_name);
+ if (!p) {
+ log_oom();
+ return;
+ }
+
+ (void) unlink(p);
+ }
+}
diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h
index e82a824f29..deebd8e484 100644
--- a/src/resolve/resolved-manager.h
+++ b/src/resolve/resolved-manager.h
@@ -46,6 +46,7 @@ struct Manager {
ResolveSupport llmnr_support;
ResolveSupport mdns_support;
DnssecMode dnssec_mode;
+ bool enable_cache;
/* Network */
Hashmap *links;
@@ -72,7 +73,6 @@ struct Manager {
LIST_HEAD(DnsSearchDomain, search_domains);
unsigned n_search_domains;
- bool permit_domain_search;
bool need_builtin_fallbacks:1;
@@ -120,6 +120,7 @@ struct Manager {
sd_bus_slot *prepare_for_sleep_slot;
sd_event_source *sigusr1_event_source;
+ sd_event_source *sigusr2_event_source;
unsigned n_transactions_total;
unsigned n_dnssec_verdict[_DNSSEC_VERDICT_MAX];
@@ -128,6 +129,13 @@ struct Manager {
Set* etc_hosts_by_address;
Hashmap* etc_hosts_by_name;
usec_t etc_hosts_last, etc_hosts_mtime;
+
+ /* Local DNS stub on 127.0.0.53:53 */
+ int dns_stub_udp_fd;
+ int dns_stub_tcp_fd;
+
+ sd_event_source *dns_stub_udp_event_source;
+ sd_event_source *dns_stub_tcp_event_source;
};
/* Manager */
@@ -140,7 +148,7 @@ int manager_start(Manager *m);
uint32_t manager_find_mtu(Manager *m);
int manager_write(Manager *m, int fd, DnsPacket *p);
-int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p);
+int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *destination, uint16_t port, const union in_addr_union *source, DnsPacket *p);
int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret);
int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr);
@@ -161,7 +169,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
int manager_is_own_hostname(Manager *m, const char *name);
int manager_compile_dns_servers(Manager *m, OrderedSet **servers);
-int manager_compile_search_domains(Manager *m, OrderedSet **domains);
+int manager_compile_search_domains(Manager *m, OrderedSet **domains, int filter_route);
DnssecMode manager_get_dnssec_mode(Manager *m);
bool manager_dnssec_supported(Manager *m);
@@ -169,3 +177,7 @@ bool manager_dnssec_supported(Manager *m);
void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key);
bool manager_routable(Manager *m, int family);
+
+void manager_flush_caches(Manager *m);
+
+void manager_cleanup_saved_user(Manager *m);
diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c
index fa89de4c21..31b25ca50f 100644
--- a/src/resolve/resolved-resolv-conf.c
+++ b/src/resolve/resolved-resolv-conf.c
@@ -92,7 +92,7 @@ int manager_read_resolv_conf(Manager *m) {
a = first_word(l, "nameserver");
if (a) {
- r = manager_add_dns_server_by_string(m, DNS_SERVER_SYSTEM, a);
+ r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_SYSTEM, a);
if (r < 0)
log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a);
@@ -149,9 +149,7 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
assert(f);
assert(count);
- (void) dns_server_string(s);
-
- if (!s->server_string) {
+ if (!dns_server_string(s)) {
log_warning("Our of memory, or invalid DNS address. Ignoring server.");
return;
}
@@ -160,7 +158,7 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) {
fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f);
(*count)++;
- fprintf(f, "nameserver %s\n", s->server_string);
+ fprintf(f, "nameserver %s\n", dns_server_string(s));
}
static void write_resolv_conf_search(
@@ -196,10 +194,13 @@ static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *doma
Iterator i;
fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n"
- "# Third party programs must not access this file directly, but\n"
- "# only through the symlink at /etc/resolv.conf. To manage\n"
- "# resolv.conf(5) in a different way, replace the symlink by a\n"
- "# static file or a different symlink.\n\n", f);
+ "# This is a dynamic resolv.conf file for connecting local clients directly to\n"
+ "# all known DNS servers.\n#\n"
+ "# Third party programs must not access this file directly, but only through the\n"
+ "# symlink at /etc/resolv.conf. To manage resolv.conf(5) in a different way,\n"
+ "# replace this symlink by a static file or a different symlink.\n#\n"
+ "# See systemd-resolved.service(8) for details about the supported modes of\n"
+ "# operation for /etc/resolv.conf.\n\n", f);
if (ordered_set_isempty(dns))
fputs("# No DNS servers known.\n", f);
@@ -227,29 +228,31 @@ int manager_write_resolv_conf(Manager *m) {
assert(m);
/* Read the system /etc/resolv.conf first */
- manager_read_resolv_conf(m);
+ (void) manager_read_resolv_conf(m);
/* Add the full list to a set, to filter out duplicates */
r = manager_compile_dns_servers(m, &dns);
if (r < 0)
- return r;
+ return log_warning_errno(r, "Failed to compile list of DNS servers: %m");
- r = manager_compile_search_domains(m, &domains);
+ r = manager_compile_search_domains(m, &domains, false);
if (r < 0)
- return r;
+ return log_warning_errno(r, "Failed to compile list of search domains: %m");
r = fopen_temporary_label(PRIVATE_RESOLV_CONF, PRIVATE_RESOLV_CONF, &f, &temp_path);
if (r < 0)
- return r;
+ return log_warning_errno(r, "Failed to open private resolv.conf file for writing: %m");
- fchmod(fileno(f), 0644);
+ (void) fchmod(fileno(f), 0644);
r = write_resolv_conf_contents(f, dns, domains);
- if (r < 0)
+ if (r < 0) {
+ log_error_errno(r, "Failed to write private resolv.conf contents: %m");
goto fail;
+ }
if (rename(temp_path, PRIVATE_RESOLV_CONF) < 0) {
- r = -errno;
+ r = log_error_errno(errno, "Failed to move private resolv.conf file into place: %m");
goto fail;
}
@@ -258,5 +261,6 @@ int manager_write_resolv_conf(Manager *m) {
fail:
(void) unlink(PRIVATE_RESOLV_CONF);
(void) unlink(temp_path);
+
return r;
}
diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c
index 161ea03412..deb75f9ae5 100644
--- a/src/resolve/resolved.c
+++ b/src/resolve/resolved.c
@@ -67,11 +67,15 @@ int main(int argc, char *argv[]) {
goto finish;
}
- r = drop_privileges(uid, gid, 0);
+ /* Drop privileges, but keep three caps. Note that we drop those too, later on (see below) */
+ r = drop_privileges(uid, gid,
+ (UINT64_C(1) << CAP_NET_RAW)| /* needed for SO_BINDTODEVICE */
+ (UINT64_C(1) << CAP_NET_BIND_SERVICE)| /* needed to bind on port 53 */
+ (UINT64_C(1) << CAP_SETPCAP) /* needed in order to drop the caps later */);
if (r < 0)
goto finish;
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, -1) >= 0);
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, SIGUSR2, -1) >= 0);
r = manager_new(&m);
if (r < 0) {
@@ -85,11 +89,15 @@ int main(int argc, char *argv[]) {
goto finish;
}
- /* Write finish default resolv.conf to avoid a dangling
- * symlink */
- r = manager_write_resolv_conf(m);
- if (r < 0)
- log_warning_errno(r, "Could not create "PRIVATE_RESOLV_CONF": %m");
+ /* Write finish default resolv.conf to avoid a dangling symlink */
+ (void) manager_write_resolv_conf(m);
+
+ /* Let's drop the remaining caps now */
+ r = capability_bounding_set_drop(0, true);
+ if (r < 0) {
+ log_error_errno(r, "Failed to drop remaining caps: %m");
+ goto finish;
+ }
sd_notify(false,
"READY=1\n"
diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in
index a288588924..3bd8389c88 100644
--- a/src/resolve/resolved.conf.in
+++ b/src/resolve/resolved.conf.in
@@ -17,3 +17,4 @@
#Domains=
#LLMNR=yes
#DNSSEC=@DEFAULT_DNSSEC_MODE@
+#Cache=yes
diff --git a/src/resolve/test-dns-packet.c b/src/resolve/test-dns-packet.c
index 41e5c1caa5..956b155872 100644
--- a/src/resolve/test-dns-packet.c
+++ b/src/resolve/test-dns-packet.c
@@ -33,6 +33,19 @@
#define HASH_KEY SD_ID128_MAKE(d3,1e,48,90,4b,fa,4c,fe,af,9d,d5,a1,d7,2e,8a,b1)
+static void verify_rr_copy(DnsResourceRecord *rr) {
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *copy = NULL;
+ const char *a, *b;
+
+ assert_se(copy = dns_resource_record_copy(rr));
+ assert_se(dns_resource_record_equal(copy, rr) > 0);
+
+ assert_se(a = dns_resource_record_to_string(rr));
+ assert_se(b = dns_resource_record_to_string(copy));
+
+ assert_se(streq(a, b));
+}
+
static uint64_t hash(DnsResourceRecord *rr) {
struct siphash state;
@@ -66,6 +79,8 @@ static void test_packet_from_file(const char* filename, bool canonical) {
assert_se(dns_packet_append_blob(p, data + offset + 8, packet_size, NULL) >= 0);
assert_se(dns_packet_read_rr(p, &rr, NULL, NULL) >= 0);
+ verify_rr_copy(rr);
+
s = dns_resource_record_to_string(rr);
assert_se(s);
puts(s);
@@ -78,6 +93,8 @@ static void test_packet_from_file(const char* filename, bool canonical) {
assert_se(dns_packet_append_blob(p2, rr->wire_format, rr->wire_format_size, NULL) >= 0);
assert_se(dns_packet_read_rr(p2, &rr2, NULL, NULL) >= 0);
+ verify_rr_copy(rr);
+
s2 = dns_resource_record_to_string(rr);
assert_se(s2);
assert_se(streq(s, s2));
diff --git a/src/run/run.c b/src/run/run.c
index d6c9b6d37a..58fa49a4d1 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -187,7 +187,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "+hrH:M:p:tq", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "+hrH:M:E:p:tq", options, NULL)) >= 0)
switch (c) {
diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c
index a86b0db554..65151b19a6 100644
--- a/src/shared/ask-password-api.c
+++ b/src/shared/ask-password-api.c
@@ -139,11 +139,7 @@ static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **pa
if (r < 0)
return r;
- /* Truncate trailing NUL */
- assert(n > 0);
- assert(p[n-1] == 0);
-
- serial = add_key("user", keyname, p, n-1, KEY_SPEC_USER_KEYRING);
+ serial = add_key("user", keyname, p, n, KEY_SPEC_USER_KEYRING);
memory_erase(p, n);
if (serial == -1)
return -errno;
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 8f4f93ee0c..589f9d46e9 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -83,18 +83,14 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
if (isempty(eq))
r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY);
- else if (endswith(eq, "%")) {
- double percent;
-
- if (sscanf(eq, "%lf%%", &percent) != 1 || percent <= 0) {
- log_error("CPU quota '%s' invalid.", eq);
+ else {
+ r = parse_percent_unbounded(eq);
+ if (r <= 0) {
+ log_error_errno(r, "CPU quota '%s' invalid.", eq);
return -EINVAL;
}
- r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) percent * USEC_PER_SEC / 100);
- } else {
- log_error("CPU quota needs to be in percent.");
- return -EINVAL;
+ r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) r * USEC_PER_SEC / 100U);
}
goto finish;
@@ -110,6 +106,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
char *n;
usec_t t;
size_t l;
+
r = parse_sec(eq, &t);
if (r < 0)
return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq);
@@ -123,6 +120,54 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
strcpy(mempcpy(n, field, l - 3), "USec");
r = sd_bus_message_append(m, "sv", n, "t", t);
goto finish;
+
+ } else if (STR_IN_SET(field, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
+ uint64_t bytes;
+
+ if (isempty(eq) || streq(eq, "infinity"))
+ bytes = CGROUP_LIMIT_MAX;
+ else {
+ r = parse_percent(eq);
+ if (r >= 0) {
+ char *n;
+
+ /* When this is a percentage we'll convert this into a relative value in the range
+ * 0…UINT32_MAX and pass it in the MemoryLowScale property (and related
+ * ones). This way the physical memory size can be determined server-side */
+
+ n = strjoina(field, "Scale");
+ r = sd_bus_message_append(m, "sv", n, "u", (uint32_t) (((uint64_t) UINT32_MAX * r) / 100U));
+ goto finish;
+
+ } else {
+ r = parse_size(eq, 1024, &bytes);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse bytes specification %s", assignment);
+ }
+ }
+
+ r = sd_bus_message_append(m, "sv", field, "t", bytes);
+ goto finish;
+ } else if (streq(field, "TasksMax")) {
+ uint64_t t;
+
+ if (isempty(eq) || streq(eq, "infinity"))
+ t = (uint64_t) -1;
+ else {
+ r = parse_percent(eq);
+ if (r >= 0) {
+ r = sd_bus_message_append(m, "sv", "TasksMaxScale", "u", (uint32_t) (((uint64_t) UINT32_MAX * r) / 100U));
+ goto finish;
+ } else {
+ r = safe_atou64(eq, &t);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse maximum tasks specification %s", assignment);
+ }
+
+ }
+
+ r = sd_bus_message_append(m, "sv", "TasksMax", "t", t);
+ goto finish;
}
r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
@@ -154,11 +199,12 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur);
} else if (STR_IN_SET(field,
- "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting",
- "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
- "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
- "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges",
- "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute")) {
+ "CPUAccounting", "MemoryAccounting", "IOAccounting", "BlockIOAccounting", "TasksAccounting",
+ "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies",
+ "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit",
+ "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges",
+ "SyslogLevelPrefix", "Delegate", "RemainAfterElapse", "MemoryDenyWriteExecute",
+ "RestrictRealtime", "DynamicUser")) {
r = parse_boolean(eq);
if (r < 0)
@@ -166,36 +212,6 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_append(m, "v", "b", r);
- } else if (STR_IN_SET(field, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
- uint64_t bytes;
-
- if (isempty(eq) || streq(eq, "infinity"))
- bytes = CGROUP_LIMIT_MAX;
- else {
- r = parse_size(eq, 1024, &bytes);
- if (r < 0) {
- log_error("Failed to parse bytes specification %s", assignment);
- return -EINVAL;
- }
- }
-
- r = sd_bus_message_append(m, "v", "t", bytes);
-
- } else if (streq(field, "TasksMax")) {
- uint64_t n;
-
- if (isempty(eq) || streq(eq, "infinity"))
- n = (uint64_t) -1;
- else {
- r = safe_atou64(eq, &n);
- if (r < 0) {
- log_error("Failed to parse maximum tasks specification %s", assignment);
- return -EINVAL;
- }
- }
-
- r = sd_bus_message_append(m, "v", "t", n);
-
} else if (STR_IN_SET(field, "CPUShares", "StartupCPUShares")) {
uint64_t u;
@@ -443,7 +459,8 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
}
r = sd_bus_message_append(m, "v", "i", oa);
- } else if (STR_IN_SET(field, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) {
+ } else if (STR_IN_SET(field, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
+ "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
const char *p;
r = sd_bus_message_open_container(m, 'v', "as");
@@ -1113,7 +1130,8 @@ static int dump_processes(
assert(n == cg->n_children);
qsort_safe(children, n, sizeof(struct CGroupInfo*), cgroup_info_compare_func);
- n_columns = MAX(LESS_BY(n_columns, 2U), 20U);
+ if (n_columns != 0)
+ n_columns = MAX(LESS_BY(n_columns, 2U), 20U);
for (i = 0; i < n; i++) {
_cleanup_free_ char *pp = NULL;
diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c
index 4efbf3710f..52410999cf 100644
--- a/src/shared/bus-util.c
+++ b/src/shared/bus-util.c
@@ -1048,7 +1048,7 @@ static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_
case SD_BUS_TYPE_BOOLEAN: {
unsigned b;
- bool *p = userdata;
+ int *p = userdata;
r = sd_bus_message_read_basic(m, type, &b);
if (r < 0)
@@ -1085,6 +1085,19 @@ static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_
break;
}
+ case SD_BUS_TYPE_DOUBLE: {
+ double d;
+ double *p = userdata;
+
+ r = sd_bus_message_read_basic(m, type, &d);
+ if (r < 0)
+ break;
+
+ *p = d;
+
+ break;
+ }
+
default:
break;
}
@@ -1492,40 +1505,6 @@ int bus_path_decode_unique(const char *path, const char *prefix, char **ret_send
return 1;
}
-bool is_kdbus_wanted(void) {
- _cleanup_free_ char *value = NULL;
-#ifdef ENABLE_KDBUS
- const bool configured = true;
-#else
- const bool configured = false;
-#endif
-
- int r;
-
- if (get_proc_cmdline_key("kdbus", NULL) > 0)
- return true;
-
- r = get_proc_cmdline_key("kdbus=", &value);
- if (r <= 0)
- return configured;
-
- return parse_boolean(value) == 1;
-}
-
-bool is_kdbus_available(void) {
- _cleanup_close_ int fd = -1;
- struct kdbus_cmd cmd = { .size = sizeof(cmd), .flags = KDBUS_FLAG_NEGOTIATE };
-
- if (!is_kdbus_wanted())
- return false;
-
- fd = open("/sys/fs/kdbus/control", O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOCTTY);
- if (fd < 0)
- return false;
-
- return ioctl(fd, KDBUS_CMD_BUS_MAKE, &cmd) >= 0;
-}
-
int bus_property_get_rlimit(
sd_bus *bus,
const char *path,
diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h
index d792258ecd..db6b1acba2 100644
--- a/src/shared/bus-util.h
+++ b/src/shared/bus-util.h
@@ -157,7 +157,4 @@ int bus_log_create_error(int r);
int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id, const char *external_id, char **ret_path);
int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external);
-bool is_kdbus_wanted(void);
-bool is_kdbus_available(void);
-
int bus_property_get_rlimit(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error);
diff --git a/src/shared/condition.c b/src/shared/condition.c
index 3a45ed265c..6bb42c0692 100644
--- a/src/shared/condition.c
+++ b/src/shared/condition.c
@@ -182,10 +182,11 @@ static int condition_test_architecture(Condition *c) {
if (streq(c->parameter, "native"))
b = native_architecture();
- else
+ else {
b = architecture_from_string(c->parameter);
- if (b < 0)
- return b;
+ if (b < 0) /* unknown architecture? Then it's definitely not ours */
+ return false;
+ }
return a == b;
}
diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c
index 83be79a4f5..7cf222e4d2 100644
--- a/src/shared/conf-parser.c
+++ b/src/shared/conf-parser.c
@@ -323,8 +323,7 @@ int config_parse(const char *unit,
if (feof(f))
break;
- log_error_errno(errno, "Failed to read configuration file '%s': %m", filename);
- return -errno;
+ return log_error_errno(errno, "Failed to read configuration file '%s': %m", filename);
}
l = buf;
@@ -708,6 +707,7 @@ int config_parse_strv(const char *unit,
void *userdata) {
char ***sv = data;
+ int r;
assert(filename);
assert(lvalue);
@@ -721,18 +721,19 @@ int config_parse_strv(const char *unit,
* we actually fill in a real empty array here rather
* than NULL, since some code wants to know if
* something was set at all... */
- empty = strv_new(NULL, NULL);
+ empty = new0(char*, 1);
if (!empty)
return log_oom();
strv_free(*sv);
*sv = empty;
+
return 0;
}
for (;;) {
char *word = NULL;
- int r;
+
r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES|EXTRACT_RETAIN_ESCAPE);
if (r == 0)
break;
diff --git a/src/basic/fdset.c b/src/shared/fdset.c
index 527f27bc67..527f27bc67 100644
--- a/src/basic/fdset.c
+++ b/src/shared/fdset.c
diff --git a/src/basic/fdset.h b/src/shared/fdset.h
index 16efe5bdf2..16efe5bdf2 100644
--- a/src/basic/fdset.h
+++ b/src/shared/fdset.h
diff --git a/src/shared/install.c b/src/shared/install.c
index 64d66a45d3..7b49e1ece9 100644
--- a/src/shared/install.c
+++ b/src/shared/install.c
@@ -779,7 +779,7 @@ static int find_symlinks(
fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
if (fd < 0) {
- if (errno == ENOENT)
+ if (IN_SET(errno, ENOENT, ENOTDIR, EACCES))
return 0;
return -errno;
}
@@ -1271,7 +1271,7 @@ static int unit_file_search(
info->path = path;
path = NULL;
return r;
- } else if (r != -ENOENT)
+ } else if (!IN_SET(r, -ENOENT, -ENOTDIR, -EACCES))
return r;
}
@@ -1296,7 +1296,7 @@ static int unit_file_search(
info->path = path;
path = NULL;
return r;
- } else if (r != -ENOENT)
+ } else if (!IN_SET(r, -ENOENT, -ENOTDIR, -EACCES))
return r;
}
}
@@ -2215,7 +2215,7 @@ int unit_file_enable(
config_path = runtime ? paths.runtime_config : paths.persistent_config;
STRV_FOREACH(f, files) {
- r = install_info_discover(scope, &c, &paths, *f, SEARCH_LOAD, &i);
+ r = install_info_discover(scope, &c, &paths, *f, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i);
if (r < 0)
return r;
r = install_info_may_process(i, &paths, changes, n_changes);
@@ -2870,6 +2870,10 @@ int unit_file_get_list(
if (!d) {
if (errno == ENOENT)
continue;
+ if (IN_SET(errno, ENOTDIR, EACCES)) {
+ log_debug("Failed to open \"%s\": %m", *i);
+ continue;
+ }
return -errno;
}
diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c
index 9351b85eed..f9d9c4ed62 100644
--- a/src/shared/logs-show.c
+++ b/src/shared/logs-show.c
@@ -45,6 +45,7 @@
#include "parse-util.h"
#include "process-util.h"
#include "sparse-endian.h"
+#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
#include "terminal-util.h"
@@ -206,6 +207,108 @@ static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, Output
return ellipsized;
}
+static int output_timestamp_monotonic(FILE *f, sd_journal *j, const char *monotonic) {
+ sd_id128_t boot_id;
+ uint64_t t;
+ int r;
+
+ assert(f);
+ assert(j);
+
+ r = -ENXIO;
+ if (monotonic)
+ r = safe_atou64(monotonic, &t);
+ if (r < 0)
+ r = sd_journal_get_monotonic_usec(j, &t, &boot_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get monotonic timestamp: %m");
+
+ fprintf(f, "[%5llu.%06llu]",
+ (unsigned long long) (t / USEC_PER_SEC),
+ (unsigned long long) (t % USEC_PER_SEC));
+
+ return 1 + 5 + 1 + 6 + 1;
+}
+
+static int output_timestamp_realtime(FILE *f, sd_journal *j, OutputMode mode, OutputFlags flags, const char *realtime) {
+ char buf[MAX(FORMAT_TIMESTAMP_MAX, 64)];
+ struct tm *(*gettime_r)(const time_t *, struct tm *);
+ struct tm tm;
+ uint64_t x;
+ time_t t;
+ int r;
+
+ assert(f);
+ assert(j);
+
+ r = -ENXIO;
+ if (realtime)
+ r = safe_atou64(realtime, &x);
+ if (r < 0)
+ r = sd_journal_get_realtime_usec(j, &x);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get realtime timestamp: %m");
+
+ if (mode == OUTPUT_SHORT_FULL) {
+ const char *k;
+
+ if (flags & OUTPUT_UTC)
+ k = format_timestamp_utc(buf, sizeof(buf), x);
+ else
+ k = format_timestamp(buf, sizeof(buf), x);
+ if (!k) {
+ log_error("Failed to format timestamp.");
+ return -EINVAL;
+ }
+
+ } else {
+ gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r;
+ t = (time_t) (x / USEC_PER_SEC);
+
+ switch (mode) {
+
+ case OUTPUT_SHORT_UNIX:
+ xsprintf(buf, "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC));
+ break;
+
+ case OUTPUT_SHORT_ISO:
+ if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)) <= 0) {
+ log_error("Failed for format ISO time");
+ return -EINVAL;
+ }
+ break;
+
+ case OUTPUT_SHORT:
+ case OUTPUT_SHORT_PRECISE:
+
+ if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)) <= 0) {
+ log_error("Failed to format syslog time");
+ return -EINVAL;
+ }
+
+ if (mode == OUTPUT_SHORT_PRECISE) {
+ size_t k;
+
+ assert(sizeof(buf) > strlen(buf));
+ k = sizeof(buf) - strlen(buf);
+
+ r = snprintf(buf + strlen(buf), k, ".%06llu", (unsigned long long) (x % USEC_PER_SEC));
+ if (r <= 0 || (size_t) r >= k) { /* too long? */
+ log_error("Failed to format precise time");
+ return -EINVAL;
+ }
+ }
+ break;
+
+ default:
+ assert_not_reached("Unknown time format");
+ }
+ }
+
+ fputs(buf, f);
+ return (int) strlen(buf);
+}
+
static int output_short(
FILE *f,
sd_journal *j,
@@ -305,78 +408,15 @@ static int output_short(
if (priority_len == 1 && *priority >= '0' && *priority <= '7')
p = *priority - '0';
- if (mode == OUTPUT_SHORT_MONOTONIC) {
- uint64_t t;
- sd_id128_t boot_id;
-
- r = -ENOENT;
-
- if (monotonic)
- r = safe_atou64(monotonic, &t);
-
- if (r < 0)
- r = sd_journal_get_monotonic_usec(j, &t, &boot_id);
-
- if (r < 0)
- return log_error_errno(r, "Failed to get monotonic timestamp: %m");
-
- fprintf(f, "[%5llu.%06llu]",
- (unsigned long long) (t / USEC_PER_SEC),
- (unsigned long long) (t % USEC_PER_SEC));
-
- n += 1 + 5 + 1 + 6 + 1;
-
- } else {
- char buf[64];
- uint64_t x;
- time_t t;
- struct tm tm;
- struct tm *(*gettime_r)(const time_t *, struct tm *);
-
- r = -ENOENT;
- gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r;
-
- if (realtime)
- r = safe_atou64(realtime, &x);
-
- if (r < 0)
- r = sd_journal_get_realtime_usec(j, &x);
-
- if (r < 0)
- return log_error_errno(r, "Failed to get realtime timestamp: %m");
-
- t = (time_t) (x / USEC_PER_SEC);
-
- switch (mode) {
-
- case OUTPUT_SHORT_UNIX:
- r = snprintf(buf, sizeof(buf), "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC));
- break;
-
- case OUTPUT_SHORT_ISO:
- r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm));
- break;
-
- case OUTPUT_SHORT_PRECISE:
- r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm));
- if (r > 0)
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%06llu", (unsigned long long) (x % USEC_PER_SEC));
- break;
-
- default:
- r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm));
- }
-
- if (r <= 0) {
- log_error("Failed to format time.");
- return -EINVAL;
- }
-
- fputs(buf, f);
- n += strlen(buf);
- }
+ if (mode == OUTPUT_SHORT_MONOTONIC)
+ r = output_timestamp_monotonic(f, j, monotonic);
+ else
+ r = output_timestamp_realtime(f, j, mode, flags, realtime);
+ if (r < 0)
+ return r;
+ n += r;
- if (hostname && (flags & OUTPUT_NO_HOSTNAME)) {
+ if (flags & OUTPUT_NO_HOSTNAME) {
/* Suppress display of the hostname if this is requested. */
hostname = NULL;
hostname_len = 0;
@@ -489,7 +529,7 @@ static int output_verbose(
off = ANSI_NORMAL;
}
- if (flags & OUTPUT_SHOW_ALL ||
+ if ((flags & OUTPUT_SHOW_ALL) ||
(((length < PRINT_CHAR_THRESHOLD) || flags & OUTPUT_FULL_WIDTH)
&& utf8_is_printable(data, length))) {
fprintf(f, " %s%.*s=", on, fieldlen, (const char*)data);
@@ -607,7 +647,7 @@ void json_escape(
if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD)
fputs("null", f);
- else if (!utf8_is_printable(p, l)) {
+ else if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(p, l)) {
bool not_first = false;
fputs("[ ", f);
@@ -910,6 +950,7 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])(
[OUTPUT_SHORT_PRECISE] = output_short,
[OUTPUT_SHORT_MONOTONIC] = output_short,
[OUTPUT_SHORT_UNIX] = output_short,
+ [OUTPUT_SHORT_FULL] = output_short,
[OUTPUT_VERBOSE] = output_verbose,
[OUTPUT_EXPORT] = output_export,
[OUTPUT_JSON] = output_json,
diff --git a/src/shared/output-mode.c b/src/shared/output-mode.c
index bec53ee0ae..67d8208ad2 100644
--- a/src/shared/output-mode.c
+++ b/src/shared/output-mode.c
@@ -22,6 +22,7 @@
static const char *const output_mode_table[_OUTPUT_MODE_MAX] = {
[OUTPUT_SHORT] = "short",
+ [OUTPUT_SHORT_FULL] = "short-full",
[OUTPUT_SHORT_ISO] = "short-iso",
[OUTPUT_SHORT_PRECISE] = "short-precise",
[OUTPUT_SHORT_MONOTONIC] = "short-monotonic",
diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h
index f37189e57f..ff29dafcb5 100644
--- a/src/shared/output-mode.h
+++ b/src/shared/output-mode.h
@@ -23,6 +23,7 @@
typedef enum OutputMode {
OUTPUT_SHORT,
+ OUTPUT_SHORT_FULL,
OUTPUT_SHORT_ISO,
OUTPUT_SHORT_PRECISE,
OUTPUT_SHORT_MONOTONIC,
diff --git a/src/shared/path-lookup.c b/src/shared/path-lookup.c
index ca593b6963..862096ae7b 100644
--- a/src/shared/path-lookup.c
+++ b/src/shared/path-lookup.c
@@ -88,7 +88,7 @@ static int user_data_dir(char **ret, const char *suffix) {
assert(suffix);
/* We don't treat /etc/xdg/systemd here as the spec
- * suggests because we assume that that is a link to
+ * suggests because we assume that is a link to
* /etc/systemd/ anyway. */
e = getenv("XDG_DATA_HOME");
diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c
index 30d22d2242..8656d112b8 100644
--- a/src/shared/seccomp-util.c
+++ b/src/shared/seccomp-util.c
@@ -95,7 +95,31 @@ const SystemCallFilterSet syscall_filter_sets[] = {
.set_name = "@clock",
.value =
"adjtimex\0"
+ "clock_adjtime\0"
+ "clock_settime\0"
"settimeofday\0"
+ "stime\0"
+ }, {
+ /* CPU emulation calls */
+ .set_name = "@cpu-emulation",
+ .value =
+ "modify_ldt\0"
+ "subpage_prot\0"
+ "switch_endian\0"
+ "vm86\0"
+ "vm86old\0"
+ }, {
+ /* Debugging/Performance Monitoring/Tracing */
+ .set_name = "@debug",
+ .value =
+ "lookup_dcookie\0"
+ "perf_event_open\0"
+ "process_vm_readv\0"
+ "process_vm_writev\0"
+ "ptrace\0"
+ "rtas\0"
+ "s390_runtime_instr\0"
+ "sys_debug_setcontext\0"
}, {
/* Default list */
.set_name = "@default",
@@ -148,10 +172,16 @@ const SystemCallFilterSet syscall_filter_sets[] = {
"shmdt\0"
"shmget\0"
}, {
+ /* Keyring */
+ .set_name = "@keyring",
+ .value =
+ "add_key\0"
+ "keyctl\0"
+ "request_key\0"
+ }, {
/* Kernel module control */
.set_name = "@module",
.value =
- "create_module\0"
"delete_module\0"
"finit_module\0"
"init_module\0"
@@ -197,40 +227,26 @@ const SystemCallFilterSet syscall_filter_sets[] = {
"_sysctl\0"
"afs_syscall\0"
"break\0"
- "fattach\0"
- "fdetach\0"
+ "create_module\0"
"ftime\0"
"get_kernel_syms\0"
- "get_mempolicy\0"
- "getmsg\0"
"getpmsg\0"
"gtty\0"
- "isastream\0"
"lock\0"
- "madvise1\0"
- "modify_ldt\0"
"mpx\0"
- "pciconfig_iobase\0"
- "perf_event_open\0"
"prof\0"
"profil\0"
- "putmsg\0"
"putpmsg\0"
"query_module\0"
- "rtas\0"
- "s390_runtime_instr\0"
"security\0"
"sgetmask\0"
"ssetmask\0"
"stty\0"
- "subpage_prot\0"
- "switch_endian\0"
- "sys_debug_setcontext\0"
+ "sysfs\0"
"tuxcall\0"
"ulimit\0"
"uselib\0"
- "vm86\0"
- "vm86old\0"
+ "ustat\0"
"vserver\0"
}, {
/* Nice grab-bag of all system calls which need superuser capabilities */
@@ -242,6 +258,7 @@ const SystemCallFilterSet syscall_filter_sets[] = {
"acct\0"
"bdflush\0"
"bpf\0"
+ "capset\0"
"chown32\0"
"chown\0"
"chroot\0"
@@ -268,7 +285,6 @@ const SystemCallFilterSet syscall_filter_sets[] = {
"setreuid\0"
"setuid32\0"
"setuid\0"
- "stime\0"
"swapoff\0"
"swapon\0"
"sysctl\0"
@@ -295,6 +311,7 @@ const SystemCallFilterSet syscall_filter_sets[] = {
.value =
"ioperm\0"
"iopl\0"
+ "pciconfig_iobase\0"
"pciconfig_read\0"
"pciconfig_write\0"
"s390_pci_mmio_read\0"
diff --git a/src/shared/vlan-util.c b/src/shared/vlan-util.c
new file mode 100644
index 0000000000..78d66dd3d9
--- /dev/null
+++ b/src/shared/vlan-util.c
@@ -0,0 +1,69 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "vlan-util.h"
+#include "parse-util.h"
+#include "conf-parser.h"
+
+int parse_vlanid(const char *p, uint16_t *ret) {
+ uint16_t id;
+ int r;
+
+ r = safe_atou16(p, &id);
+ if (r < 0)
+ return r;
+ if (!vlanid_is_valid(id))
+ return -ERANGE;
+
+ *ret = id;
+ return 0;
+}
+
+int config_parse_vlanid(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ uint16_t *id = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = parse_vlanid(rvalue, id);
+ if (r == -ERANGE) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "VLAN identifier outside of valid range 0…4094, ignoring: %s", rvalue);
+ return 0;
+ }
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse VLAN identifier value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ return 0;
+}
diff --git a/src/shared/vlan-util.h b/src/shared/vlan-util.h
new file mode 100644
index 0000000000..ce6763b3a3
--- /dev/null
+++ b/src/shared/vlan-util.h
@@ -0,0 +1,35 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <inttypes.h>
+
+#define VLANID_MAX 4094
+#define VLANID_INVALID UINT16_MAX
+
+/* Note that we permit VLAN Id 0 here, as that is apparently OK by the Linux kernel */
+static inline bool vlanid_is_valid(uint16_t id) {
+ return id <= VLANID_MAX;
+}
+
+int parse_vlanid(const char *p, uint16_t *ret);
+
+int config_parse_vlanid(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index df41182529..b4ce6fba5a 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -174,6 +174,7 @@ static bool arg_firmware_setup = false;
static bool arg_now = false;
static int daemon_reload(int argc, char *argv[], void* userdata);
+static int trivial_method(int argc, char *argv[], void *userdata);
static int halt_now(enum action a);
static int get_state_one_unit(sd_bus *bus, const char *name, UnitActiveState *active_state);
@@ -223,6 +224,21 @@ static void release_busses(void) {
busses[w] = sd_bus_flush_close_unref(busses[w]);
}
+static int map_string_no_copy(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
+ char *s;
+ const char **p = userdata;
+ int r;
+
+ r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &s);
+ if (r < 0)
+ return r;
+
+ if (!isempty(s))
+ *p = s;
+
+ return 0;
+}
+
static void ask_password_agent_open_if_enabled(void) {
/* Open the password agent as a child process if necessary */
@@ -587,7 +603,8 @@ static int get_unit_list(
return bus_log_create_error(r);
r = sd_bus_call(bus, m, 0, &error, &reply);
- if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
+ if (r < 0 && (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD) ||
+ sd_bus_error_has_name(&error, SD_BUS_ERROR_ACCESS_DENIED))) {
/* Fallback to legacy ListUnitsFiltered method */
fallback = true;
log_debug_errno(r, "Failed to list units: %s Falling back to ListUnitsFiltered method.", bus_error_message(&error, r));
@@ -733,12 +750,12 @@ static int list_units(int argc, char *argv[], void *userdata) {
sd_bus *bus;
int r;
- pager_open(arg_no_pager, false);
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ pager_open(arg_no_pager, false);
+
r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines);
if (r < 0)
return r;
@@ -945,12 +962,12 @@ static int list_sockets(int argc, char *argv[], void *userdata) {
int r = 0, n;
sd_bus *bus;
- pager_open(arg_no_pager, false);
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ pager_open(arg_no_pager, false);
+
n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines);
if (n < 0)
return n;
@@ -1252,12 +1269,12 @@ static int list_timers(int argc, char *argv[], void *userdata) {
sd_bus *bus;
int r = 0;
- pager_open(arg_no_pager, false);
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ pager_open(arg_no_pager, false);
+
n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines);
if (n < 0)
return n;
@@ -1423,8 +1440,6 @@ static int list_unit_files(int argc, char *argv[], void *userdata) {
int r;
bool fallback = false;
- pager_open(arg_no_pager, false);
-
if (install_client_side()) {
Hashmap *h;
UnitFileList *u;
@@ -1539,6 +1554,8 @@ static int list_unit_files(int argc, char *argv[], void *userdata) {
return bus_log_parse_error(r);
}
+ pager_open(arg_no_pager, false);
+
qsort_safe(units, c, sizeof(UnitFileList), compare_unit_file_list);
output_unit_file_list(units, c);
@@ -1787,12 +1804,12 @@ static int list_dependencies(int argc, char *argv[], void *userdata) {
} else
u = SPECIAL_DEFAULT_TARGET;
- pager_open(arg_no_pager, false);
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ pager_open(arg_no_pager, false);
+
puts(u);
return list_dependencies_one(bus, u, 0, &units, 0);
@@ -1818,12 +1835,12 @@ static const struct bus_properties_map machine_info_property_map[] = {
};
static void machine_info_clear(struct machine_info *info) {
- if (info) {
- free(info->name);
- free(info->state);
- free(info->control_group);
- zero(*info);
- }
+ assert(info);
+
+ free(info->name);
+ free(info->state);
+ free(info->control_group);
+ zero(*info);
}
static void free_machines_list(struct machine_info *machine_infos, int n) {
@@ -2018,8 +2035,6 @@ static int list_machines(int argc, char *argv[], void *userdata) {
return -EPERM;
}
- pager_open(arg_no_pager, false);
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
@@ -2028,6 +2043,8 @@ static int list_machines(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
+ pager_open(arg_no_pager, false);
+
qsort_safe(machine_infos, r, sizeof(struct machine_info), compare_machine_info);
output_machines_list(machine_infos, r);
free_machines_list(machine_infos, r);
@@ -2231,8 +2248,6 @@ static int list_jobs(int argc, char *argv[], void *userdata) {
int r;
bool skipped = false;
- pager_open(arg_no_pager, false);
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
@@ -2273,6 +2288,8 @@ static int list_jobs(int argc, char *argv[], void *userdata) {
if (r < 0)
return bus_log_parse_error(r);
+ pager_open(arg_no_pager, false);
+
output_jobs_list(jobs, c, skipped);
return 0;
}
@@ -2283,14 +2300,14 @@ static int cancel_job(int argc, char *argv[], void *userdata) {
int r = 0;
if (argc <= 1)
- return daemon_reload(argc, argv, userdata);
-
- polkit_agent_open_if_enabled();
+ return trivial_method(argc, argv, userdata);
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ polkit_agent_open_if_enabled();
+
STRV_FOREACH(name, strv_skip(argv, 1)) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
uint32_t id;
@@ -2497,7 +2514,7 @@ static int unit_find_paths(
r = 1;
}
- if (r == 0)
+ if (r == 0 && !arg_force)
log_error("No files found for %s.", unit_name);
return r;
@@ -2693,10 +2710,9 @@ static int start_unit_one(
if (r < 0) {
const char *verb;
- if (r == -ENOENT && arg_action != ACTION_SYSTEMCTL)
- /* There's always a fallback possible for
- * legacy actions. */
- return -EADDRNOTAVAIL;
+ /* There's always a fallback possible for legacy actions. */
+ if (arg_action != ACTION_SYSTEMCTL)
+ return r;
verb = method_to_verb(method);
@@ -2827,13 +2843,13 @@ static int start_unit(int argc, char *argv[], void *userdata) {
char **name;
int r = 0;
- ask_password_agent_open_if_enabled();
- polkit_agent_open_if_enabled();
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ ask_password_agent_open_if_enabled();
+ polkit_agent_open_if_enabled();
+
if (arg_action == ACTION_SYSTEMCTL) {
enum action action;
@@ -2953,9 +2969,6 @@ static int logind_reboot(enum action a) {
sd_bus *bus;
int r;
- polkit_agent_open_if_enabled();
- (void) logind_set_wall_message();
-
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
@@ -2991,6 +3004,9 @@ static int logind_reboot(enum action a) {
return -EINVAL;
}
+ polkit_agent_open_if_enabled();
+ (void) logind_set_wall_message();
+
r = sd_bus_call_method(
bus,
"org.freedesktop.login1",
@@ -3032,6 +3048,9 @@ static int logind_check_inhibitors(enum action a) {
if (!on_tty())
return 0;
+ if (arg_transport != BUS_TRANSPORT_LOCAL)
+ return 0;
+
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
@@ -3252,7 +3271,7 @@ static int start_special(int argc, char *argv[], void *userdata) {
ACTION_REBOOT,
ACTION_KEXEC,
ACTION_EXIT))
- return daemon_reload(argc, argv, userdata);
+ return trivial_method(argc, argv, userdata);
/* First try logind, to allow authentication with polkit */
if (IN_SET(a,
@@ -3274,6 +3293,18 @@ static int start_special(int argc, char *argv[], void *userdata) {
return start_unit(argc, argv, userdata);
}
+static int start_system_special(int argc, char *argv[], void *userdata) {
+ /* Like start_special above, but raises an error when running in user mode */
+
+ if (arg_scope != UNIT_FILE_SYSTEM) {
+ log_error("Bad action for %s mode.",
+ arg_scope == UNIT_FILE_GLOBAL ? "--global" : "--user");
+ return -EINVAL;
+ }
+
+ return start_special(argc, argv, userdata);
+}
+
static int check_unit_generic(int code, const UnitActiveState good_states[], int nb_states, char **args) {
_cleanup_strv_free_ char **names = NULL;
UnitActiveState active_state;
@@ -3325,12 +3356,12 @@ static int kill_unit(int argc, char *argv[], void *userdata) {
sd_bus *bus;
int r, q;
- polkit_agent_open_if_enabled();
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ polkit_agent_open_if_enabled();
+
if (!arg_kill_who)
arg_kill_who = "all";
@@ -3442,6 +3473,27 @@ static int exec_status_info_deserialize(sd_bus_message *m, ExecStatusInfo *i) {
return 1;
}
+typedef struct UnitCondition {
+ char *name;
+ char *param;
+ bool trigger;
+ bool negate;
+ int tristate;
+
+ LIST_FIELDS(struct UnitCondition, conditions);
+} UnitCondition;
+
+static void unit_condition_free(UnitCondition *c) {
+ if (!c)
+ return;
+
+ free(c->name);
+ free(c->param);
+ free(c);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(UnitCondition*, unit_condition_free);
+
typedef struct UnitStatusInfo {
const char *id;
const char *load_state;
@@ -3488,10 +3540,7 @@ typedef struct UnitStatusInfo {
usec_t condition_timestamp;
bool condition_result;
- bool failed_condition_trigger;
- bool failed_condition_negate;
- const char *failed_condition;
- const char *failed_condition_parameter;
+ LIST_HEAD(UnitCondition, conditions);
usec_t assert_timestamp;
bool assert_result;
@@ -3530,6 +3579,25 @@ typedef struct UnitStatusInfo {
LIST_HEAD(ExecStatusInfo, exec);
} UnitStatusInfo;
+static void unit_status_info_free(UnitStatusInfo *info) {
+ ExecStatusInfo *p;
+ UnitCondition *c;
+
+ strv_free(info->documentation);
+ strv_free(info->dropin_paths);
+ strv_free(info->listen);
+
+ while ((c = info->conditions)) {
+ LIST_REMOVE(conditions, info->conditions, c);
+ unit_condition_free(c);
+ }
+
+ while ((p = info->exec)) {
+ LIST_REMOVE(exec, info->exec, p);
+ exec_status_info_free(p);
+ }
+}
+
static void print_status_info(
sd_bus *bus,
UnitStatusInfo *i,
@@ -3651,19 +3719,28 @@ static void print_status_info(
printf("\n");
if (!i->condition_result && i->condition_timestamp > 0) {
+ UnitCondition *c;
+ int n = 0;
+
s1 = format_timestamp_relative(since1, sizeof(since1), i->condition_timestamp);
s2 = format_timestamp(since2, sizeof(since2), i->condition_timestamp);
printf("Condition: start %scondition failed%s at %s%s%s\n",
ansi_highlight_yellow(), ansi_normal(),
s2, s1 ? "; " : "", strempty(s1));
- if (i->failed_condition_trigger)
- printf(" none of the trigger conditions were met\n");
- else if (i->failed_condition)
- printf(" %s=%s%s was not met\n",
- i->failed_condition,
- i->failed_condition_negate ? "!" : "",
- i->failed_condition_parameter);
+
+ LIST_FOREACH(conditions, c, i->conditions)
+ if (c->tristate < 0)
+ n++;
+
+ LIST_FOREACH(conditions, c, i->conditions)
+ if (c->tristate < 0)
+ printf(" %s %s=%s%s%s was not met\n",
+ --n ? special_glyph(TREE_BRANCH) : special_glyph(TREE_RIGHT),
+ c->name,
+ c->trigger ? "|" : "",
+ c->negate ? "!" : "",
+ c->param);
}
if (!i->assert_result && i->assert_timestamp > 0) {
@@ -3748,7 +3825,7 @@ static void print_status_info(
if (i->running) {
_cleanup_free_ char *comm = NULL;
- get_process_comm(i->main_pid, &comm);
+ (void) get_process_comm(i->main_pid, &comm);
if (comm)
printf(" (%s)", comm);
} else if (i->exit_code > 0) {
@@ -3767,17 +3844,19 @@ static void print_status_info(
printf("signal=%s", signal_to_string(i->exit_status));
printf(")");
}
-
- if (i->control_pid > 0)
- printf(";");
}
if (i->control_pid > 0) {
_cleanup_free_ char *c = NULL;
- printf(" %8s: "PID_FMT, i->main_pid ? "" : " Control", i->control_pid);
+ if (i->main_pid > 0)
+ fputs("; Control PID: ", stdout);
+ else
+ fputs("Cntrl PID: ", stdout); /* if first in column, abbreviated so it fits alignment */
+
+ printf(PID_FMT, i->control_pid);
- get_process_comm(i->control_pid, &c);
+ (void) get_process_comm(i->control_pid, &c);
if (c)
printf(" (%s)", c);
}
@@ -3794,7 +3873,7 @@ static void print_status_info(
printf(" Tasks: %" PRIu64, i->tasks_current);
if (i->tasks_max != (uint64_t) -1)
- printf(" (limit: %" PRIi64 ")\n", i->tasks_max);
+ printf(" (limit: %" PRIu64 ")\n", i->tasks_max);
else
printf("\n");
}
@@ -3835,14 +3914,13 @@ static void print_status_info(
printf(" CPU: %s\n", format_timespan(buf, sizeof(buf), i->cpu_usage_nsec / NSEC_PER_USEC, USEC_PER_MSEC));
}
- if (i->control_group)
- printf(" CGroup: %s\n", i->control_group);
-
- {
+ if (i->control_group) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
static const char prefix[] = " ";
unsigned c;
+ printf(" CGroup: %s\n", i->control_group);
+
c = columns();
if (c > sizeof(prefix) - 1)
c -= sizeof(prefix) - 1;
@@ -4157,13 +4235,25 @@ static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo *
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(m, "(sbbsi)", &cond, &trigger, &negate, &param, &state)) > 0) {
- log_debug("%s %d %d %s %d", cond, trigger, negate, param, state);
- if (state < 0 && (!trigger || !i->failed_condition)) {
- i->failed_condition = cond;
- i->failed_condition_trigger = trigger;
- i->failed_condition_negate = negate;
- i->failed_condition_parameter = param;
- }
+ _cleanup_(unit_condition_freep) UnitCondition *c = NULL;
+
+ log_debug("%s trigger=%d negate=%d %s →%d", cond, trigger, negate, param, state);
+
+ c = new0(UnitCondition, 1);
+ if (!c)
+ return log_oom();
+
+ c->name = strdup(cond);
+ c->param = strdup(param);
+ if (!c->name || !c->param)
+ return log_oom();
+
+ c->trigger = trigger;
+ c->negate = negate;
+ c->tristate = state;
+
+ LIST_PREPEND(conditions, i->conditions, c);
+ c = NULL;
}
if (r < 0)
return bus_log_parse_error(r);
@@ -4355,7 +4445,7 @@ static int print_property(const char *name, sd_bus_message *m, const char *conte
return bus_log_parse_error(r);
while ((r = sd_bus_message_read(m, "(sb)", &path, &ignore)) > 0)
- print_prop("EnvironmentFile", "%s (ignore_errors=%s)\n", path, yes_no(ignore));
+ print_prop("EnvironmentFile", "%s (ignore_errors=%s)", path, yes_no(ignore));
if (r < 0)
return bus_log_parse_error(r);
@@ -4547,13 +4637,21 @@ static int show_one(
const char *verb,
sd_bus *bus,
const char *path,
+ const char *unit,
bool show_properties,
bool *new_line,
bool *ellipsized) {
+ static const struct bus_properties_map property_map[] = {
+ { "LoadState", "s", map_string_no_copy, offsetof(UnitStatusInfo, load_state) },
+ { "ActiveState", "s", map_string_no_copy, offsetof(UnitStatusInfo, active_state) },
+ {}
+ };
+
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- UnitStatusInfo info = {
+ _cleanup_set_free_ Set *found_properties = NULL;
+ _cleanup_(unit_status_info_free) UnitStatusInfo info = {
.memory_current = (uint64_t) -1,
.memory_high = CGROUP_LIMIT_MAX,
.memory_max = CGROUP_LIMIT_MAX,
@@ -4562,15 +4660,6 @@ static int show_one(
.tasks_current = (uint64_t) -1,
.tasks_max = (uint64_t) -1,
};
- struct property_info {
- const char *load_state, *active_state;
- } property_info = {};
- static const struct bus_properties_map property_map[] = {
- { "LoadState", "s", NULL, offsetof(struct property_info, load_state) },
- { "ActiveState", "s", NULL, offsetof(struct property_info, active_state) },
- {}
- };
- ExecStatusInfo *p;
int r;
assert(path);
@@ -4590,16 +4679,24 @@ static int show_one(
if (r < 0)
return log_error_errno(r, "Failed to get properties: %s", bus_error_message(&error, r));
- r = bus_message_map_all_properties(reply, property_map, &property_info);
- if (r < 0)
- return log_error_errno(r, "Failed to map properties: %s", bus_error_message(&error, r));
+ if (unit) {
+ r = bus_message_map_all_properties(reply, property_map, &info);
+ if (r < 0)
+ return log_error_errno(r, "Failed to map properties: %s", bus_error_message(&error, r));
- if (streq_ptr(property_info.load_state, "not-found") && streq_ptr(property_info.active_state, "inactive"))
- return EXIT_PROGRAM_OR_SERVICES_STATUS_UNKNOWN;
+ if (streq_ptr(info.load_state, "not-found") && streq_ptr(info.active_state, "inactive")) {
+ log_error("Unit %s could not be found.", unit);
- r = sd_bus_message_rewind(reply, true);
- if (r < 0)
- return log_error_errno(r, "Failed to rewind: %s", bus_error_message(&error, r));
+ if (streq(verb, "status"))
+ return EXIT_PROGRAM_OR_SERVICES_STATUS_UNKNOWN;
+
+ return -ENOENT;
+ }
+
+ r = sd_bus_message_rewind(reply, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to rewind: %s", bus_error_message(&error, r));
+ }
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}");
if (r < 0)
@@ -4625,9 +4722,17 @@ static int show_one(
if (r < 0)
return bus_log_parse_error(r);
- if (show_properties)
+ if (show_properties) {
+ r = set_ensure_allocated(&found_properties, &string_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(found_properties, name);
+ if (r < 0 && r != EEXIST)
+ return log_oom();
+
r = print_property(name, reply, contents);
- else
+ } else
r = status_property(name, reply, &info, contents);
if (r < 0)
return r;
@@ -4648,39 +4753,24 @@ static int show_one(
return bus_log_parse_error(r);
r = 0;
+ if (show_properties) {
+ char **pp;
- if (!show_properties) {
- if (streq(verb, "help"))
- show_unit_help(&info);
- else
- print_status_info(bus, &info, ellipsized);
- }
-
- strv_free(info.documentation);
- strv_free(info.dropin_paths);
- strv_free(info.listen);
-
- if (!streq_ptr(info.active_state, "active") &&
- !streq_ptr(info.active_state, "reloading") &&
- streq(verb, "status")) {
- /* According to LSB: "program not running" */
- /* 0: program is running or service is OK
- * 1: program is dead and /run PID file exists
- * 2: program is dead and /run/lock lock file exists
- * 3: program is not running
- * 4: program or service status is unknown
- */
- if (info.pid_file && access(info.pid_file, F_OK) == 0)
- r = EXIT_PROGRAM_DEAD_AND_PID_EXISTS;
- else if (streq_ptr(info.load_state, "not-found") && streq_ptr(info.active_state, "inactive"))
- r = EXIT_PROGRAM_OR_SERVICES_STATUS_UNKNOWN;
- else
- r = EXIT_PROGRAM_NOT_RUNNING;
- }
+ STRV_FOREACH(pp, arg_properties)
+ if (!set_contains(found_properties, *pp)) {
+ log_warning("Property %s does not exist.", *pp);
+ r = -ENXIO;
+ }
- while ((p = info.exec)) {
- LIST_REMOVE(exec, info.exec, p);
- exec_status_info_free(p);
+ } else if (streq(verb, "help"))
+ show_unit_help(&info);
+ else if (streq(verb, "status")) {
+ print_status_info(bus, &info, ellipsized);
+
+ if (info.active_state && !STR_IN_SET(info.active_state, "active", "reloading"))
+ r = EXIT_PROGRAM_NOT_RUNNING;
+ else
+ r = EXIT_PROGRAM_RUNNING_OR_SERVICE_OK;
}
return r;
@@ -4750,7 +4840,7 @@ static int show_all(
if (!p)
return log_oom();
- r = show_one(verb, bus, p, show_properties, new_line, ellipsized);
+ r = show_one(verb, bus, p, u->id, show_properties, new_line, ellipsized);
if (r < 0)
return r;
else if (r > 0 && ret == 0)
@@ -4832,6 +4922,10 @@ static int show(int argc, char *argv[], void *userdata) {
return -EINVAL;
}
+ r = acquire_bus(BUS_MANAGER, &bus);
+ if (r < 0)
+ return r;
+
pager_open(arg_no_pager, false);
if (show_status)
@@ -4840,17 +4934,12 @@ static int show(int argc, char *argv[], void *userdata) {
* be split up into many files. */
setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(16384));
- r = acquire_bus(BUS_MANAGER, &bus);
- if (r < 0)
- return r;
-
/* If no argument is specified inspect the manager itself */
if (show_properties && argc <= 1)
- return show_one(argv[0], bus, "/org/freedesktop/systemd1", show_properties, &new_line, &ellipsized);
+ return show_one(argv[0], bus, "/org/freedesktop/systemd1", NULL, show_properties, &new_line, &ellipsized);
if (show_status && argc <= 1) {
- pager_open(arg_no_pager, false);
show_system_status(bus);
new_line = true;
@@ -4861,7 +4950,7 @@ static int show(int argc, char *argv[], void *userdata) {
char **name;
STRV_FOREACH(name, strv_skip(argv, 1)) {
- _cleanup_free_ char *unit = NULL;
+ _cleanup_free_ char *path = NULL, *unit = NULL;
uint32_t id;
if (safe_atou32(*name, &id) < 0) {
@@ -4871,19 +4960,23 @@ static int show(int argc, char *argv[], void *userdata) {
continue;
} else if (show_properties) {
/* Interpret as job id */
- if (asprintf(&unit, "/org/freedesktop/systemd1/job/%u", id) < 0)
+ if (asprintf(&path, "/org/freedesktop/systemd1/job/%u", id) < 0)
return log_oom();
} else {
/* Interpret as PID */
- r = get_unit_dbus_path_by_pid(bus, id, &unit);
+ r = get_unit_dbus_path_by_pid(bus, id, &path);
if (r < 0) {
ret = r;
continue;
}
+
+ r = unit_name_from_dbus_path(path, &unit);
+ if (r < 0)
+ return log_oom();
}
- r = show_one(argv[0], bus, unit, show_properties, &new_line, &ellipsized);
+ r = show_one(argv[0], bus, path, unit, show_properties, &new_line, &ellipsized);
if (r < 0)
return r;
else if (r > 0 && ret == 0)
@@ -4898,20 +4991,17 @@ static int show(int argc, char *argv[], void *userdata) {
return log_error_errno(r, "Failed to expand names: %m");
STRV_FOREACH(name, names) {
- _cleanup_free_ char *unit;
+ _cleanup_free_ char *path;
- unit = unit_dbus_path_from_name(*name);
- if (!unit)
+ path = unit_dbus_path_from_name(*name);
+ if (!path)
return log_oom();
- r = show_one(argv[0], bus, unit, show_properties, &new_line, &ellipsized);
+ r = show_one(argv[0], bus, path, *name, show_properties, &new_line, &ellipsized);
if (r < 0)
return r;
- else if (r > 0 && ret == 0)
+ if (r > 0 && ret == 0)
ret = r;
-
- if (r == EXIT_PROGRAM_OR_SERVICES_STATUS_UNKNOWN)
- log_error("Can't display property %s. Unit %s does not exist.", *patterns, *name);
}
}
}
@@ -5006,12 +5096,12 @@ static int set_property(int argc, char *argv[], void *userdata) {
char **i;
int r;
- polkit_agent_open_if_enabled();
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ polkit_agent_open_if_enabled();
+
r = sd_bus_message_new_method_call(
bus,
&m,
@@ -5053,36 +5143,87 @@ static int set_property(int argc, char *argv[], void *userdata) {
static int daemon_reload(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
const char *method;
sd_bus *bus;
int r;
- polkit_agent_open_if_enabled();
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
- if (arg_action == ACTION_RELOAD)
+ polkit_agent_open_if_enabled();
+
+ switch (arg_action) {
+
+ case ACTION_RELOAD:
method = "Reload";
- else if (arg_action == ACTION_REEXEC)
+ break;
+
+ case ACTION_REEXEC:
method = "Reexecute";
- else {
- assert(arg_action == ACTION_SYSTEMCTL);
+ break;
+
+ case ACTION_SYSTEMCTL:
+ method = streq(argv[0], "daemon-reexec") ? "Reexecute" :
+ /* "daemon-reload" */ "Reload";
+ break;
- method =
- streq(argv[0], "clear-jobs") ||
- streq(argv[0], "cancel") ? "ClearJobs" :
- streq(argv[0], "daemon-reexec") ? "Reexecute" :
- streq(argv[0], "reset-failed") ? "ResetFailed" :
- streq(argv[0], "halt") ? "Halt" :
- streq(argv[0], "poweroff") ? "PowerOff" :
- streq(argv[0], "reboot") ? "Reboot" :
- streq(argv[0], "kexec") ? "KExec" :
- streq(argv[0], "exit") ? "Exit" :
- /* "daemon-reload" */ "Reload";
+ default:
+ assert_not_reached("Unexpected action");
}
+ r = sd_bus_message_new_method_call(
+ bus,
+ &m,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ method);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ /* Note we use an extra-long timeout here. This is because a reload or reexec means generators are rerun which
+ * are timed out after DEFAULT_TIMEOUT_USEC. Let's use twice that time here, so that the generators can have
+ * their timeout, and for everything else there's the same time budget in place. */
+
+ r = sd_bus_call(bus, m, DEFAULT_TIMEOUT_USEC * 2, &error, NULL);
+
+ /* On reexecution, we expect a disconnect, not a reply */
+ if (IN_SET(r, -ETIMEDOUT, -ECONNRESET) && streq(method, "Reexecute"))
+ r = 0;
+
+ if (r < 0 && arg_action == ACTION_SYSTEMCTL)
+ return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
+
+ /* Note that for the legacy commands (i.e. those with action != ACTION_SYSTEMCTL) we support fallbacks to the
+ * old ways of doing things, hence don't log any error in that case here. */
+
+ return r < 0 ? r : 0;
+}
+
+static int trivial_method(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ const char *method;
+ sd_bus *bus;
+ int r;
+
+ r = acquire_bus(BUS_MANAGER, &bus);
+ if (r < 0)
+ return r;
+
+ polkit_agent_open_if_enabled();
+
+ method =
+ streq(argv[0], "clear-jobs") ||
+ streq(argv[0], "cancel") ? "ClearJobs" :
+ streq(argv[0], "reset-failed") ? "ResetFailed" :
+ streq(argv[0], "halt") ? "Halt" :
+ streq(argv[0], "reboot") ? "Reboot" :
+ streq(argv[0], "kexec") ? "KExec" :
+ streq(argv[0], "exit") ? "Exit" :
+ /* poweroff */ "PowerOff";
+
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
@@ -5092,16 +5233,11 @@ static int daemon_reload(int argc, char *argv[], void *userdata) {
&error,
NULL,
NULL);
- if (r == -ENOENT && arg_action != ACTION_SYSTEMCTL)
- /* There's always a fallback possible for
- * legacy actions. */
- r = -EADDRNOTAVAIL;
- else if ((r == -ETIMEDOUT || r == -ECONNRESET) && streq(method, "Reexecute"))
- /* On reexecution, we expect a disconnect, not a
- * reply */
- r = 0;
- else if (r < 0)
- return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));
+ if (r < 0 && arg_action == ACTION_SYSTEMCTL)
+ return log_error_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r));
+
+ /* Note that for the legacy commands (i.e. those with action != ACTION_SYSTEMCTL) we support fallbacks to the
+ * old ways of doing things, hence don't log any error in that case here. */
return r < 0 ? r : 0;
}
@@ -5113,14 +5249,14 @@ static int reset_failed(int argc, char *argv[], void *userdata) {
int r, q;
if (argc <= 1)
- return daemon_reload(argc, argv, userdata);
-
- polkit_agent_open_if_enabled();
+ return trivial_method(argc, argv, userdata);
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ polkit_agent_open_if_enabled();
+
r = expand_names(bus, strv_skip(argv, 1), NULL, &names);
if (r < 0)
return log_error_errno(r, "Failed to expand names: %m");
@@ -5154,12 +5290,12 @@ static int show_environment(int argc, char *argv[], void *userdata) {
sd_bus *bus;
int r;
- pager_open(arg_no_pager, false);
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ pager_open(arg_no_pager, false);
+
r = sd_bus_get_property(
bus,
"org.freedesktop.systemd1",
@@ -5263,12 +5399,12 @@ static int set_environment(int argc, char *argv[], void *userdata) {
assert(argc > 1);
assert(argv);
- polkit_agent_open_if_enabled();
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ polkit_agent_open_if_enabled();
+
method = streq(argv[0], "set-environment")
? "SetEnvironment"
: "UnsetEnvironment";
@@ -5300,12 +5436,12 @@ static int import_environment(int argc, char *argv[], void *userdata) {
sd_bus *bus;
int r;
- polkit_agent_open_if_enabled();
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ polkit_agent_open_if_enabled();
+
r = sd_bus_message_new_method_call(
bus,
&m,
@@ -5430,10 +5566,12 @@ static int enable_sysv_units(const char *verb, char **args) {
if (!found_sysv)
continue;
- if (found_native)
- log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]);
- else
- log_info("%s is not a native service, redirecting to systemd-sysv-install.", name);
+ if (!arg_quiet) {
+ if (found_native)
+ log_info("Synchronizing state of %s with SysV service script with %s.", name, argv[0]);
+ else
+ log_info("%s is not a native service, redirecting to systemd-sysv-install.", name);
+ }
if (!isempty(arg_root))
argv[c++] = q = strappend("--root=", arg_root);
@@ -5463,10 +5601,8 @@ static int enable_sysv_units(const char *verb, char **args) {
}
j = wait_for_terminate(pid, &status);
- if (j < 0) {
- log_error_errno(j, "Failed to wait for child: %m");
- return j;
- }
+ if (j < 0)
+ return log_error_errno(j, "Failed to wait for child: %m");
if (status.si_code == CLD_EXITED) {
if (streq(verb, "is-enabled")) {
@@ -5536,6 +5672,69 @@ static int mangle_names(char **original_names, char ***mangled_names) {
return 0;
}
+static int normalize_names(char **names, bool warn_if_path) {
+ char **u;
+ bool was_path = false;
+
+ STRV_FOREACH(u, names) {
+ int r;
+
+ if (!is_path(*u))
+ continue;
+
+ r = free_and_strdup(u, basename(*u));
+ if (r < 0)
+ return log_error_errno(r, "Failed to normalize unit file path: %m");
+
+ was_path = true;
+ }
+
+ if (warn_if_path && was_path)
+ log_warning("Warning: Can't execute disable on the unit file path. Proceeding with the unit name.");
+
+ return 0;
+}
+
+static int unit_exists(const char *unit) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *path = NULL;
+ static const struct bus_properties_map property_map[] = {
+ { "LoadState", "s", map_string_no_copy, offsetof(UnitStatusInfo, load_state) },
+ { "ActiveState", "s", map_string_no_copy, offsetof(UnitStatusInfo, active_state)},
+ {},
+ };
+ UnitStatusInfo info = {};
+ sd_bus *bus;
+ int r;
+
+ path = unit_dbus_path_from_name(unit);
+ if (!path)
+ return log_oom();
+
+ r = acquire_bus(BUS_MANAGER, &bus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ &error,
+ &reply,
+ "s", "");
+ if (r < 0)
+ return log_error_errno(r, "Failed to get properties: %s", bus_error_message(&error, r));
+
+ r = bus_message_map_all_properties(reply, property_map, &info);
+ if (r < 0)
+ return log_error_errno(r, "Failed to map properties: %s", bus_error_message(&error, r));
+
+ return !streq_ptr(info.load_state, "not-found") || !streq_ptr(info.active_state, "inactive");
+}
+
static int enable_unit(int argc, char *argv[], void *userdata) {
_cleanup_strv_free_ char **names = NULL;
const char *verb = argv[0];
@@ -5563,6 +5762,12 @@ static int enable_unit(int argc, char *argv[], void *userdata) {
return daemon_reload(argc, argv, userdata);
}
+ if (streq(verb, "disable")) {
+ r = normalize_names(names, true);
+ if (r < 0)
+ return r;
+ }
+
if (install_client_side()) {
if (streq(verb, "enable")) {
r = unit_file_enable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes);
@@ -5597,12 +5802,20 @@ static int enable_unit(int argc, char *argv[], void *userdata) {
const char *method;
sd_bus *bus;
- polkit_agent_open_if_enabled();
+ if (STR_IN_SET(verb, "mask", "unmask")) {
+ r = unit_exists(*names);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ log_notice("Unit %s does not exist, proceeding anyway.", *names);
+ }
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ polkit_agent_open_if_enabled();
+
if (streq(verb, "enable")) {
method = "EnableUnitFiles";
expect_carries_install_info = true;
@@ -5763,12 +5976,12 @@ static int add_dependency(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
- polkit_agent_open_if_enabled();
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ polkit_agent_open_if_enabled();
+
r = sd_bus_message_new_method_call(
bus,
&m,
@@ -5825,12 +6038,12 @@ static int preset_all(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
sd_bus *bus;
- polkit_agent_open_if_enabled();
-
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
+ polkit_agent_open_if_enabled();
+
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
@@ -6001,7 +6214,7 @@ static int create_edit_temp_file(const char *new_path, const char *original_path
return log_error_errno(r, "Failed to create temporary file \"%s\": %m", t);
} else if (r < 0)
- return log_error_errno(r, "Failed to copy \"%s\" to \"%s\": %m", original_path, t);
+ return log_error_errno(r, "Failed to create temporary file for \"%s\": %m", new_path);
*ret_tmp_fn = t;
t = NULL;
@@ -6024,7 +6237,7 @@ static int get_file_to_edit(
return log_oom();
if (arg_runtime) {
- run = strjoin(paths->runtime_config, name, NULL);
+ run = strjoin(paths->runtime_config, "/", name, NULL);
if (!run)
return log_oom();
}
@@ -6045,9 +6258,10 @@ static int get_file_to_edit(
return 0;
}
-static int unit_file_create_dropin(
+static int unit_file_create_new(
const LookupPaths *paths,
const char *unit_name,
+ const char *suffix,
char **ret_new_path,
char **ret_tmp_path) {
@@ -6058,7 +6272,7 @@ static int unit_file_create_dropin(
assert(ret_new_path);
assert(ret_tmp_path);
- ending = strjoina(unit_name, ".d/override.conf");
+ ending = strjoina(unit_name, suffix);
r = get_file_to_edit(paths, ending, &tmp_new_path);
if (r < 0)
return r;
@@ -6105,13 +6319,12 @@ static int unit_file_create_copy(
if (response != 'y') {
log_warning("%s ignored", unit_name);
free(tmp_new_path);
- return -1;
+ return -EKEYREJECTED;
}
}
r = create_edit_temp_file(tmp_new_path, fragment_path, &tmp_tmp_path);
if (r < 0) {
- log_error_errno(r, "Failed to create temporary file for \"%s\": %m", tmp_new_path);
free(tmp_new_path);
return r;
}
@@ -6222,18 +6435,24 @@ static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) {
r = unit_find_paths(bus, *name, &lp, &path, NULL);
if (r < 0)
return r;
- else if (r == 0)
- return -ENOENT;
- else if (!path) {
- // FIXME: support units with path==NULL (no FragmentPath)
- log_error("No fragment exists for %s.", *name);
- return -ENOENT;
+ else if (!arg_force) {
+ if (r == 0) {
+ log_error("Run 'systemctl edit --force %s' to create a new unit.", *name);
+ return -ENOENT;
+ } else if (!path) {
+ // FIXME: support units with path==NULL (no FragmentPath)
+ log_error("No fragment exists for %s.", *name);
+ return -ENOENT;
+ }
}
- if (arg_full)
- r = unit_file_create_copy(&lp, *name, path, &new_path, &tmp_path);
- else
- r = unit_file_create_dropin(&lp, *name, &new_path, &tmp_path);
+ if (path) {
+ if (arg_full)
+ r = unit_file_create_copy(&lp, *name, path, &new_path, &tmp_path);
+ else
+ r = unit_file_create_new(&lp, *name, ".d/override.conf", &new_path, &tmp_path);
+ } else
+ r = unit_file_create_new(&lp, *name, NULL, &new_path, &tmp_path);
if (r < 0)
return r;
@@ -6372,9 +6591,9 @@ static void systemctl_help(void) {
" --preset-mode= Apply only enable, only disable, or all presets\n"
" --root=PATH Enable unit files in the specified root directory\n"
" -n --lines=INTEGER Number of journal entries to show\n"
- " -o --output=STRING Change journal output mode (short, short-iso,\n"
- " short-precise, short-monotonic, verbose,\n"
- " export, json, json-pretty, json-sse, cat)\n"
+ " -o --output=STRING Change journal output mode (short, short-precise,\n"
+ " short-iso, short-full, short-monotonic, short-unix,\n"
+ " verbose, export, json, json-pretty, json-sse, cat)\n"
" --firmware-setup Tell the firmware to show the setup menu on next boot\n"
" --plain Print unit dependencies as a list instead of a tree\n\n"
"Unit Commands:\n"
@@ -6407,7 +6626,7 @@ static void systemctl_help(void) {
" unit is required or wanted\n\n"
"Unit File Commands:\n"
" list-unit-files [PATTERN...] List installed unit files\n"
- " enable NAME... Enable one or more unit files\n"
+ " enable [NAME...|PATH...] Enable one or more unit files\n"
" disable NAME... Disable one or more unit files\n"
" reenable NAME... Reenable one or more unit files\n"
" preset NAME... Enable/disable one or more unit files\n"
@@ -7493,71 +7712,71 @@ static int systemctl_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_NOCHROOT, list_units },
- { "list-unit-files", VERB_ANY, VERB_ANY, 0, list_unit_files },
- { "list-sockets", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_sockets },
- { "list-timers", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_timers },
- { "list-jobs", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_jobs },
- { "list-machines", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_machines },
- { "clear-jobs", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload },
- { "cancel", VERB_ANY, VERB_ANY, VERB_NOCHROOT, cancel_job },
- { "start", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
- { "stop", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
- { "condstop", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */
- { "reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
- { "restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
- { "try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
- { "reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
- { "reload-or-try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatbility with old systemctl <= 228 */
- { "try-reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
- { "force-reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with SysV */
- { "condreload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */
- { "condrestart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with RH */
- { "isolate", 2, 2, VERB_NOCHROOT, start_unit },
- { "kill", 2, VERB_ANY, VERB_NOCHROOT, kill_unit },
- { "is-active", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active },
- { "check", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active },
- { "is-failed", 2, VERB_ANY, VERB_NOCHROOT, check_unit_failed },
- { "show", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show },
- { "cat", 2, VERB_ANY, VERB_NOCHROOT, cat },
- { "status", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show },
- { "help", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show },
- { "daemon-reload", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload },
- { "daemon-reexec", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload },
- { "show-environment", VERB_ANY, 1, VERB_NOCHROOT, show_environment },
- { "set-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment },
- { "unset-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment },
- { "import-environment", VERB_ANY, VERB_ANY, VERB_NOCHROOT, import_environment},
- { "halt", VERB_ANY, 1, VERB_NOCHROOT, start_special },
- { "poweroff", VERB_ANY, 1, VERB_NOCHROOT, start_special },
- { "reboot", VERB_ANY, 2, VERB_NOCHROOT, start_special },
- { "kexec", VERB_ANY, 1, VERB_NOCHROOT, start_special },
- { "suspend", VERB_ANY, 1, VERB_NOCHROOT, start_special },
- { "hibernate", VERB_ANY, 1, VERB_NOCHROOT, start_special },
- { "hybrid-sleep", VERB_ANY, 1, VERB_NOCHROOT, start_special },
- { "default", VERB_ANY, 1, VERB_NOCHROOT, start_special },
- { "rescue", VERB_ANY, 1, VERB_NOCHROOT, start_special },
- { "emergency", VERB_ANY, 1, VERB_NOCHROOT, start_special },
- { "exit", VERB_ANY, 2, VERB_NOCHROOT, start_special },
- { "reset-failed", VERB_ANY, VERB_ANY, VERB_NOCHROOT, reset_failed },
- { "enable", 2, VERB_ANY, 0, enable_unit },
- { "disable", 2, VERB_ANY, 0, enable_unit },
- { "is-enabled", 2, VERB_ANY, 0, unit_is_enabled },
- { "reenable", 2, VERB_ANY, 0, enable_unit },
- { "preset", 2, VERB_ANY, 0, enable_unit },
- { "preset-all", VERB_ANY, 1, 0, preset_all },
- { "mask", 2, VERB_ANY, 0, enable_unit },
- { "unmask", 2, VERB_ANY, 0, enable_unit },
- { "link", 2, VERB_ANY, 0, enable_unit },
- { "revert", 2, VERB_ANY, 0, enable_unit },
- { "switch-root", 2, VERB_ANY, VERB_NOCHROOT, switch_root },
- { "list-dependencies", VERB_ANY, 2, VERB_NOCHROOT, list_dependencies },
- { "set-default", 2, 2, 0, set_default },
- { "get-default", VERB_ANY, 1, 0, get_default },
- { "set-property", 3, VERB_ANY, VERB_NOCHROOT, set_property },
- { "is-system-running", VERB_ANY, 1, 0, is_system_running },
- { "add-wants", 3, VERB_ANY, 0, add_dependency },
- { "add-requires", 3, VERB_ANY, 0, add_dependency },
- { "edit", 2, VERB_ANY, VERB_NOCHROOT, edit },
+ { "list-unit-files", VERB_ANY, VERB_ANY, 0, list_unit_files },
+ { "list-sockets", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_sockets },
+ { "list-timers", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_timers },
+ { "list-jobs", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_jobs },
+ { "list-machines", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_machines },
+ { "clear-jobs", VERB_ANY, 1, VERB_NOCHROOT, trivial_method },
+ { "cancel", VERB_ANY, VERB_ANY, VERB_NOCHROOT, cancel_job },
+ { "start", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
+ { "stop", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
+ { "condstop", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */
+ { "reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
+ { "restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
+ { "try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
+ { "reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
+ { "reload-or-try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatbility with old systemctl <= 228 */
+ { "try-reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit },
+ { "force-reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with SysV */
+ { "condreload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */
+ { "condrestart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with RH */
+ { "isolate", 2, 2, VERB_NOCHROOT, start_unit },
+ { "kill", 2, VERB_ANY, VERB_NOCHROOT, kill_unit },
+ { "is-active", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active },
+ { "check", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active },
+ { "is-failed", 2, VERB_ANY, VERB_NOCHROOT, check_unit_failed },
+ { "show", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show },
+ { "cat", 2, VERB_ANY, VERB_NOCHROOT, cat },
+ { "status", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show },
+ { "help", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show },
+ { "daemon-reload", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload },
+ { "daemon-reexec", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload },
+ { "show-environment", VERB_ANY, 1, VERB_NOCHROOT, show_environment },
+ { "set-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment },
+ { "unset-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment },
+ { "import-environment", VERB_ANY, VERB_ANY, VERB_NOCHROOT, import_environment },
+ { "halt", VERB_ANY, 1, VERB_NOCHROOT, start_system_special },
+ { "poweroff", VERB_ANY, 1, VERB_NOCHROOT, start_system_special },
+ { "reboot", VERB_ANY, 2, VERB_NOCHROOT, start_system_special },
+ { "kexec", VERB_ANY, 1, VERB_NOCHROOT, start_system_special },
+ { "suspend", VERB_ANY, 1, VERB_NOCHROOT, start_system_special },
+ { "hibernate", VERB_ANY, 1, VERB_NOCHROOT, start_system_special },
+ { "hybrid-sleep", VERB_ANY, 1, VERB_NOCHROOT, start_system_special },
+ { "default", VERB_ANY, 1, VERB_NOCHROOT, start_special },
+ { "rescue", VERB_ANY, 1, VERB_NOCHROOT, start_system_special },
+ { "emergency", VERB_ANY, 1, VERB_NOCHROOT, start_system_special },
+ { "exit", VERB_ANY, 2, VERB_NOCHROOT, start_special },
+ { "reset-failed", VERB_ANY, VERB_ANY, VERB_NOCHROOT, reset_failed },
+ { "enable", 2, VERB_ANY, 0, enable_unit },
+ { "disable", 2, VERB_ANY, 0, enable_unit },
+ { "is-enabled", 2, VERB_ANY, 0, unit_is_enabled },
+ { "reenable", 2, VERB_ANY, 0, enable_unit },
+ { "preset", 2, VERB_ANY, 0, enable_unit },
+ { "preset-all", VERB_ANY, 1, 0, preset_all },
+ { "mask", 2, VERB_ANY, 0, enable_unit },
+ { "unmask", 2, VERB_ANY, 0, enable_unit },
+ { "link", 2, VERB_ANY, 0, enable_unit },
+ { "revert", 2, VERB_ANY, 0, enable_unit },
+ { "switch-root", 2, VERB_ANY, VERB_NOCHROOT, switch_root },
+ { "list-dependencies", VERB_ANY, 2, VERB_NOCHROOT, list_dependencies },
+ { "set-default", 2, 2, 0, set_default },
+ { "get-default", VERB_ANY, 1, 0, get_default },
+ { "set-property", 3, VERB_ANY, VERB_NOCHROOT, set_property },
+ { "is-system-running", VERB_ANY, 1, 0, is_system_running },
+ { "add-wants", 3, VERB_ANY, 0, add_dependency },
+ { "add-requires", 3, VERB_ANY, 0, add_dependency },
+ { "edit", 2, VERB_ANY, VERB_NOCHROOT, edit },
{}
};
@@ -7571,7 +7790,7 @@ static int reload_with_fallback(void) {
return 0;
/* Nothing else worked, so let's try signals */
- assert(arg_action == ACTION_RELOAD || arg_action == ACTION_REEXEC);
+ assert(IN_SET(arg_action, ACTION_RELOAD, ACTION_REEXEC));
if (kill(1, arg_action == ACTION_RELOAD ? SIGHUP : SIGTERM) < 0)
return log_error_errno(errno, "kill() failed: %m");
@@ -7585,8 +7804,7 @@ static int start_with_fallback(void) {
if (start_unit(0, NULL, NULL) >= 0)
return 0;
- /* Nothing else worked, so let's try
- * /dev/initctl */
+ /* Nothing else worked, so let's try /dev/initctl */
if (talk_initctl() > 0)
return 0;
@@ -7652,8 +7870,6 @@ static int logind_schedule_shutdown(void) {
sd_bus *bus;
int r;
- (void) logind_set_wall_message();
-
r = acquire_bus(BUS_FULL, &bus);
if (r < 0)
return r;
@@ -7680,6 +7896,8 @@ static int logind_schedule_shutdown(void) {
if (arg_dry)
action = strjoina("dry-", action);
+ (void) logind_set_wall_message();
+
r = sd_bus_call_method(
bus,
"org.freedesktop.login1",
@@ -7870,6 +8088,8 @@ int main(int argc, char*argv[]) {
}
finish:
+ release_busses();
+
pager_close();
ask_password_agent_close();
polkit_agent_close();
@@ -7881,8 +8101,6 @@ finish:
strv_free(arg_wall);
free(arg_root);
- release_busses();
-
/* Note that we return r here, not EXIT_SUCCESS, so that we can implement the LSB-like return codes */
return r < 0 ? EXIT_FAILURE : r;
}
diff --git a/src/systemd/sd-daemon.h b/src/systemd/sd-daemon.h
index e6787b0a64..740b176903 100644
--- a/src/systemd/sd-daemon.h
+++ b/src/systemd/sd-daemon.h
@@ -196,6 +196,11 @@ int sd_is_mq(int fd, const char *path);
invocation. This variable is only supported with
sd_pid_notify_with_fds().
+ WATCHDOG_USEC=...
+ Reset watchdog_usec value during runtime.
+ To reset watchdog_usec value, start the service again.
+ Example: "WATCHDOG_USEC=20000000"
+
Daemons can choose to send additional variables. However, it is
recommended to prefix variable names not listed above with X_.
diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h
index 531ace1c34..cc26b7df55 100644
--- a/src/systemd/sd-event.h
+++ b/src/systemd/sd-event.h
@@ -104,6 +104,7 @@ int sd_event_get_tid(sd_event *e, pid_t *tid);
int sd_event_get_exit_code(sd_event *e, int *code);
int sd_event_set_watchdog(sd_event *e, int b);
int sd_event_get_watchdog(sd_event *e);
+int sd_event_get_iteration(sd_event *e, uint64_t *ret);
sd_event_source* sd_event_source_ref(sd_event_source *s);
sd_event_source* sd_event_source_unref(sd_event_source *s);
diff --git a/src/systemd/sd-lldp.h b/src/systemd/sd-lldp.h
index 5772d5794a..3f35eebea3 100644
--- a/src/systemd/sd-lldp.h
+++ b/src/systemd/sd-lldp.h
@@ -23,6 +23,7 @@
#include <inttypes.h>
#include <net/ethernet.h>
+#include <sys/types.h>
#include "sd-event.h"
@@ -30,9 +31,6 @@
_SD_BEGIN_DECLARATIONS;
-typedef struct sd_lldp sd_lldp;
-typedef struct sd_lldp_neighbor sd_lldp_neighbor;
-
/* IEEE 802.3AB Clause 9: TLV Types */
enum {
SD_LLDP_TYPE_END = 0,
@@ -111,6 +109,9 @@ enum {
SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION = 7,
};
+typedef struct sd_lldp sd_lldp;
+typedef struct sd_lldp_neighbor sd_lldp_neighbor;
+
typedef enum sd_lldp_event {
SD_LLDP_EVENT_ADDED = 'a',
SD_LLDP_EVENT_REMOVED = 'r',
@@ -120,7 +121,8 @@ typedef enum sd_lldp_event {
typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata);
-int sd_lldp_new(sd_lldp **ret, int ifindex);
+int sd_lldp_new(sd_lldp **ret);
+sd_lldp* sd_lldp_ref(sd_lldp *lldp);
sd_lldp* sd_lldp_unref(sd_lldp *lldp);
int sd_lldp_start(sd_lldp *lldp);
@@ -128,8 +130,10 @@ int sd_lldp_stop(sd_lldp *lldp);
int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority);
int sd_lldp_detach_event(sd_lldp *lldp);
+sd_event *sd_lldp_get_event(sd_lldp *lldp);
int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata);
+int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex);
/* Controls how much and what to store in the neighbors database */
int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t n);
@@ -145,6 +149,7 @@ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n);
/* Access to LLDP frame metadata */
int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address);
int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address);
+int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret);
int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */
@@ -152,7 +157,7 @@ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const vo
int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret);
-int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret);
+int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec);
int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret);
diff --git a/src/systemd/sd-ndisc.h b/src/systemd/sd-ndisc.h
index 2b774233b8..9f7d4ef71a 100644
--- a/src/systemd/sd-ndisc.h
+++ b/src/systemd/sd-ndisc.h
@@ -22,6 +22,8 @@
#include <inttypes.h>
#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
#include "sd-event.h"
@@ -29,55 +31,99 @@
_SD_BEGIN_DECLARATIONS;
+/* Neightbor Discovery Options, RFC 4861, Section 4.6 and
+ * https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5 */
enum {
- SD_NDISC_EVENT_STOP = 0,
- SD_NDISC_EVENT_TIMEOUT = 1,
+ SD_NDISC_OPTION_SOURCE_LL_ADDRESS = 1,
+ SD_NDISC_OPTION_TARGET_LL_ADDRESS = 2,
+ SD_NDISC_OPTION_PREFIX_INFORMATION = 3,
+ SD_NDISC_OPTION_MTU = 5,
+ SD_NDISC_OPTION_ROUTE_INFORMATION = 24,
+ SD_NDISC_OPTION_RDNSS = 25,
+ SD_NDISC_OPTION_FLAGS_EXTENSION = 26,
+ SD_NDISC_OPTION_DNSSL = 31,
+ SD_NDISC_OPTION_CAPTIVE_PORTAL = 37,
+};
+
+/* Route preference, RFC 4191, Section 2.1 */
+enum {
+ SD_NDISC_PREFERENCE_LOW = 3U,
+ SD_NDISC_PREFERENCE_MEDIUM = 0U,
+ SD_NDISC_PREFERENCE_HIGH = 1U,
};
typedef struct sd_ndisc sd_ndisc;
+typedef struct sd_ndisc_router sd_ndisc_router;
-typedef void(*sd_ndisc_router_callback_t)(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata);
-typedef void(*sd_ndisc_prefix_onlink_callback_t)(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen,
- unsigned lifetime, void *userdata);
-typedef void(*sd_ndisc_prefix_autonomous_callback_t)(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen,
- unsigned lifetime_prefered, unsigned lifetime_valid, void *userdata);
-typedef void(*sd_ndisc_callback_t)(sd_ndisc *nd, int event, void *userdata);
-
-int sd_ndisc_set_callback(sd_ndisc *nd,
- sd_ndisc_router_callback_t rcb,
- sd_ndisc_prefix_onlink_callback_t plcb,
- sd_ndisc_prefix_autonomous_callback_t pacb,
- sd_ndisc_callback_t cb,
- void *userdata);
-int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index);
-int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr);
+typedef enum sd_ndisc_event {
+ SD_NDISC_EVENT_TIMEOUT = 't',
+ SD_NDISC_EVENT_ROUTER = 'r',
+} sd_ndisc_event;
-int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority);
-int sd_ndisc_detach_event(sd_ndisc *nd);
-sd_event *sd_ndisc_get_event(sd_ndisc *nd);
+typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata);
+int sd_ndisc_new(sd_ndisc **ret);
sd_ndisc *sd_ndisc_ref(sd_ndisc *nd);
sd_ndisc *sd_ndisc_unref(sd_ndisc *nd);
-int sd_ndisc_new(sd_ndisc **ret);
-
-int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu);
+int sd_ndisc_start(sd_ndisc *nd);
int sd_ndisc_stop(sd_ndisc *nd);
-int sd_ndisc_router_discovery_start(sd_ndisc *nd);
-#define SD_NDISC_ADDRESS_FORMAT_STR "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x"
+int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority);
+int sd_ndisc_detach_event(sd_ndisc *nd);
+sd_event *sd_ndisc_get_event(sd_ndisc *nd);
+
+int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t cb, void *userdata);
+int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index);
+int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr);
-#define SD_NDISC_ADDRESS_FORMAT_VAL(address) \
- be16toh((address).s6_addr16[0]), \
- be16toh((address).s6_addr16[1]), \
- be16toh((address).s6_addr16[2]), \
- be16toh((address).s6_addr16[3]), \
- be16toh((address).s6_addr16[4]), \
- be16toh((address).s6_addr16[5]), \
- be16toh((address).s6_addr16[6]), \
- be16toh((address).s6_addr16[7])
+int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *ret);
+int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret);
+
+int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size);
+sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt);
+sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt);
+
+int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr);
+int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret);
+int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size);
+
+int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret);
+int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags);
+int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret);
+int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime);
+int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret);
+
+/* Generic option access */
+int sd_ndisc_router_option_rewind(sd_ndisc_router *rt);
+int sd_ndisc_router_option_next(sd_ndisc_router *rt);
+int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret);
+int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type);
+int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size);
+
+/* Specific option access: SD_NDISC_OPTION_PREFIX_INFORMATION */
+int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret);
+int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret);
+int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret);
+int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr);
+int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen);
+
+/* Specific option access: SD_NDISC_OPTION_ROUTE_INFORMATION */
+int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret);
+int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr);
+int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen);
+int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret);
+
+/* Specific option access: SD_NDISC_OPTION_RDNSS */
+int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret);
+int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret);
+
+/* Specific option access: SD_NDISC_OPTION_DNSSL */
+int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret);
+int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref);
_SD_END_DECLARATIONS;
diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h
index 3ae110c080..7efa8ebe5a 100644
--- a/src/systemd/sd-netlink.h
+++ b/src/systemd/sd-netlink.h
@@ -43,7 +43,7 @@ typedef int (*sd_netlink_message_handler_t)(sd_netlink *nl, sd_netlink_message *
int sd_netlink_new_from_netlink(sd_netlink **nl, int fd);
int sd_netlink_open(sd_netlink **nl);
int sd_netlink_open_fd(sd_netlink **nl, int fd);
-int sd_netlink_inc_rcvbuf(const sd_netlink *const rtnl, const int size);
+int sd_netlink_inc_rcvbuf(sd_netlink *nl, const size_t size);
sd_netlink *sd_netlink_ref(sd_netlink *nl);
sd_netlink *sd_netlink_unref(sd_netlink *nl);
diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c
index 4377f1b910..5d72493725 100644
--- a/src/sysusers/sysusers.c
+++ b/src/sysusers/sysusers.c
@@ -1299,81 +1299,6 @@ static bool item_equal(Item *a, Item *b) {
return true;
}
-static bool valid_user_group_name(const char *u) {
- const char *i;
- long sz;
-
- if (isempty(u))
- return false;
-
- if (!(u[0] >= 'a' && u[0] <= 'z') &&
- !(u[0] >= 'A' && u[0] <= 'Z') &&
- u[0] != '_')
- return false;
-
- for (i = u+1; *i; i++) {
- if (!(*i >= 'a' && *i <= 'z') &&
- !(*i >= 'A' && *i <= 'Z') &&
- !(*i >= '0' && *i <= '9') &&
- *i != '_' &&
- *i != '-')
- return false;
- }
-
- sz = sysconf(_SC_LOGIN_NAME_MAX);
- assert_se(sz > 0);
-
- if ((size_t) (i-u) > (size_t) sz)
- return false;
-
- if ((size_t) (i-u) > UT_NAMESIZE - 1)
- return false;
-
- return true;
-}
-
-static 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;
-}
-
-static bool valid_home(const char *p) {
-
- 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_safe(p))
- return false;
-
- /* Colons are used as field separators, and hence not OK */
- if (strchr(p, ':'))
- return false;
-
- return true;
-}
-
static int parse_line(const char *fname, unsigned line, const char *buffer) {
static const Specifier specifier_table[] = {
@@ -1418,7 +1343,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
}
if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE)) {
- log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]);
+ log_error("[%s:%u] Unknown command type '%c'.", fname, line, action[0]);
return -EBADMSG;
}
diff --git a/src/sysv-generator/sysv-generator.c b/src/sysv-generator/sysv-generator.c
index 4e12071e93..3ed8f23ff9 100644
--- a/src/sysv-generator/sysv-generator.c
+++ b/src/sysv-generator/sysv-generator.c
@@ -856,7 +856,7 @@ static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_servic
_cleanup_free_ char *name = NULL, *fpath = NULL;
int a, b;
- if (de->d_name[0] != 'S' && de->d_name[0] != 'K')
+ if (de->d_name[0] != 'S')
continue;
if (strlen(de->d_name) < 4)
@@ -886,28 +886,23 @@ static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_servic
continue;
}
- if (de->d_name[0] == 'S') {
+ service->sysv_start_priority = MAX(a*10 + b, service->sysv_start_priority);
- service->sysv_start_priority = MAX(a*10 + b, service->sysv_start_priority);
-
- r = set_ensure_allocated(&runlevel_services[i], NULL);
- if (r < 0) {
- log_oom();
- goto finish;
- }
-
- r = set_put(runlevel_services[i], service);
- if (r < 0) {
- log_oom();
- goto finish;
- }
+ r = set_ensure_allocated(&runlevel_services[i], NULL);
+ if (r < 0) {
+ log_oom();
+ goto finish;
+ }
+ r = set_put(runlevel_services[i], service);
+ if (r < 0) {
+ log_oom();
+ goto finish;
}
}
}
}
-
for (i = 0; i < ELEMENTSOF(rcnd_table); i ++)
SET_FOREACH(service, runlevel_services[i], j) {
r = strv_extend(&service->before, rcnd_table[i].target);
diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c
index 5a8c6cbfb6..57d9da4855 100644
--- a/src/test/test-calendarspec.c
+++ b/src/test/test-calendarspec.c
@@ -88,15 +88,39 @@ static void test_next(const char *input, const char *new_tz, usec_t after, usec_
tzset();
}
+static void test_timestamp(void) {
+ char buf[FORMAT_TIMESTAMP_MAX];
+ _cleanup_free_ char *t = NULL;
+ CalendarSpec *c;
+ usec_t x, y;
+
+ /* Ensure that a timestamp is also a valid calendar specification. Convert forth and back */
+
+ x = now(CLOCK_REALTIME);
+
+ assert_se(format_timestamp_us(buf, sizeof(buf), x));
+ printf("%s\n", buf);
+ assert_se(calendar_spec_from_string(buf, &c) >= 0);
+ assert_se(calendar_spec_to_string(c, &t) >= 0);
+ calendar_spec_free(c);
+ printf("%s\n", t);
+
+ assert_se(parse_timestamp(t, &y) >= 0);
+ assert_se(y == x);
+}
+
int main(int argc, char* argv[]) {
CalendarSpec *c;
- test_one("Sat,Thu,Mon-Wed,Sat-Sun", "Mon-Thu,Sat,Sun *-*-* 00:00:00");
+ test_one("Sat,Thu,Mon-Wed,Sat-Sun", "Mon..Thu,Sat,Sun *-*-* 00:00:00");
+ test_one("Sat,Thu,Mon..Wed,Sat..Sun", "Mon..Thu,Sat,Sun *-*-* 00:00:00");
test_one("Mon,Sun 12-*-* 2,1:23", "Mon,Sun 2012-*-* 01,02:23:00");
test_one("Wed *-1", "Wed *-*-01 00:00:00");
test_one("Wed-Wed,Wed *-1", "Wed *-*-01 00:00:00");
+ test_one("Wed..Wed,Wed *-1", "Wed *-*-01 00:00:00");
test_one("Wed, 17:48", "Wed *-*-* 17:48:00");
- test_one("Wed-Sat,Tue 12-10-15 1:2:3", "Tue-Sat 2012-10-15 01:02:03");
+ test_one("Wed-Sat,Tue 12-10-15 1:2:3", "Tue..Sat 2012-10-15 01:02:03");
+ test_one("Wed..Sat,Tue 12-10-15 1:2:3", "Tue..Sat 2012-10-15 01:02:03");
test_one("*-*-7 0:0:0", "*-*-07 00:00:00");
test_one("10-15", "*-10-15 00:00:00");
test_one("monday *-12-* 17:00", "Mon *-12-* 17:00:00");
@@ -124,6 +148,10 @@ int main(int argc, char* argv[]) {
test_one("2016-03-27 03:17:00.4200005", "2016-03-27 03:17:00.420001");
test_one("2016-03-27 03:17:00/0.42", "2016-03-27 03:17:00/0.420000");
test_one("2016-03-27 03:17:00/0.42", "2016-03-27 03:17:00/0.420000");
+ test_one("9..11,13:00,30", "*-*-* 09,10,11,13:00,30:00");
+ test_one("1..3-1..3 1..3:1..3", "*-01,02,03-01,02,03 01,02,03:01,02,03:00");
+ test_one("00:00:1.125..2.125", "*-*-* 00:00:01.125000,02.125000");
+ test_one("00:00:1.0..3.8", "*-*-* 00:00:01,02,03");
test_next("2016-03-27 03:17:00", "", 12345, 1459048620000000);
test_next("2016-03-27 03:17:00", "CET", 12345, 1459041420000000);
@@ -146,6 +174,9 @@ int main(int argc, char* argv[]) {
assert_se(calendar_spec_from_string("2000-03-05.23 00:00:00", &c) < 0);
assert_se(calendar_spec_from_string("2000-03-05 00:00.1:00", &c) < 0);
assert_se(calendar_spec_from_string("00:00:00/0.00000001", &c) < 0);
+ assert_se(calendar_spec_from_string("00:00:00.0..00.9", &c) < 0);
+
+ test_timestamp();
return 0;
}
diff --git a/src/test/test-cgroup.c b/src/test/test-cgroup.c
index 72c32d9c8f..5336c19652 100644
--- a/src/test/test-cgroup.c
+++ b/src/test/test-cgroup.c
@@ -60,16 +60,16 @@ int main(int argc, char*argv[]) {
assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a") > 0);
assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b") == 0);
- assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", 0, false, false, false, NULL) == 0);
- assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", 0, false, false, false, NULL) > 0);
+ assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", 0, 0, NULL, NULL, NULL) == 0);
+ assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", 0, 0, NULL, NULL, NULL) > 0);
- assert_se(cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", SYSTEMD_CGROUP_CONTROLLER, "/test-a", false, false) > 0);
+ assert_se(cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", SYSTEMD_CGROUP_CONTROLLER, "/test-a", 0) > 0);
assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a") == 0);
assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b") > 0);
- assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", 0, false, false, false, NULL) > 0);
- assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", 0, false, false, false, NULL) == 0);
+ assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-a", 0, 0, NULL, NULL, NULL) > 0);
+ assert_se(cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, "/test-b", 0, 0, NULL, NULL, NULL) == 0);
cg_trim(SYSTEMD_CGROUP_CONTROLLER, "/", false);
diff --git a/src/test/test-condition.c b/src/test/test-condition.c
index 8903d10db7..66003aa6bd 100644
--- a/src/test/test-condition.c
+++ b/src/test/test-condition.c
@@ -25,6 +25,7 @@
#include "audit-util.h"
#include "condition.h"
#include "hostname-util.h"
+#include "id128-util.h"
#include "ima-util.h"
#include "log.h"
#include "macro.h"
@@ -142,9 +143,14 @@ static void test_condition_test_host(void) {
hostname = gethostname_malloc();
assert_se(hostname);
- condition = condition_new(CONDITION_HOST, hostname, false, false);
- assert_se(condition_test(condition));
- condition_free(condition);
+ /* if hostname looks like an id128 then skip testing it */
+ if (id128_is_valid(hostname))
+ log_notice("hostname is an id128, skipping test");
+ else {
+ condition = condition_new(CONDITION_HOST, hostname, false, false);
+ assert_se(condition_test(condition));
+ condition_free(condition);
+ }
}
static void test_condition_test_architecture(void) {
@@ -159,15 +165,15 @@ static void test_condition_test_architecture(void) {
assert_se(sa);
condition = condition_new(CONDITION_ARCHITECTURE, sa, false, false);
- assert_se(condition_test(condition));
+ assert_se(condition_test(condition) > 0);
condition_free(condition);
condition = condition_new(CONDITION_ARCHITECTURE, "garbage value", false, false);
- assert_se(condition_test(condition) < 0);
+ assert_se(condition_test(condition) == 0);
condition_free(condition);
condition = condition_new(CONDITION_ARCHITECTURE, sa, false, true);
- assert_se(!condition_test(condition));
+ assert_se(condition_test(condition) == 0);
condition_free(condition);
}
diff --git a/src/test/test-execute.c b/src/test/test-execute.c
index 77ef4e8b2a..1d24115b5c 100644
--- a/src/test/test-execute.c
+++ b/src/test/test-execute.c
@@ -33,6 +33,7 @@
#include "test-helper.h"
#include "unit.h"
#include "util.h"
+#include "virt.h"
typedef void (*test_function_t)(Manager *m);
@@ -91,6 +92,16 @@ static void test_exec_personality(Manager *m) {
#elif defined(__s390__)
test(m, "exec-personality-s390.service", 0, CLD_EXITED);
+#elif defined(__powerpc64__)
+# if __BYTE_ORDER == __BIG_ENDIAN
+ test(m, "exec-personality-ppc64.service", 0, CLD_EXITED);
+# else
+ test(m, "exec-personality-ppc64le.service", 0, CLD_EXITED);
+# endif
+
+#elif defined(__aarch64__)
+ test(m, "exec-personality-aarch64.service", 0, CLD_EXITED);
+
#elif defined(__i386__)
test(m, "exec-personality-x86.service", 0, CLD_EXITED);
#endif
@@ -111,6 +122,10 @@ static void test_exec_privatetmp(Manager *m) {
}
static void test_exec_privatedevices(Manager *m) {
+ if (detect_container() > 0) {
+ log_notice("testing in container, skipping private device tests");
+ return;
+ }
test(m, "exec-privatedevices-yes.service", 0, CLD_EXITED);
test(m, "exec-privatedevices-no.service", 0, CLD_EXITED);
}
diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c
index 6db2c2b6f1..e0c040f39b 100644
--- a/src/test/test-fs-util.c
+++ b/src/test/test-fs-util.c
@@ -82,10 +82,56 @@ static void test_get_files_in_directory(void) {
assert_se(get_files_in_directory(".", NULL) >= 0);
}
+static void test_var_tmp(void) {
+ char *tmp_dir = NULL;
+ char *tmpdir_backup = NULL;
+ const char *default_var_tmp = NULL;
+ const char *var_name;
+ bool do_overwrite = true;
+
+ default_var_tmp = "/var/tmp";
+ var_name = "TMPDIR";
+
+ if (getenv(var_name) != NULL) {
+ tmpdir_backup = strdup(getenv(var_name));
+ assert_se(tmpdir_backup != NULL);
+ }
+
+ unsetenv(var_name);
+
+ var_tmp(&tmp_dir);
+ assert_se(!strcmp(tmp_dir, default_var_tmp));
+
+ free(tmp_dir);
+
+ setenv(var_name, "/tmp", do_overwrite);
+ assert_se(!strcmp(getenv(var_name), "/tmp"));
+
+ var_tmp(&tmp_dir);
+ assert_se(!strcmp(tmp_dir, "/tmp"));
+
+ free(tmp_dir);
+
+ setenv(var_name, "/88_does_not_exist_88", do_overwrite);
+ assert_se(!strcmp(getenv(var_name), "/88_does_not_exist_88"));
+
+ var_tmp(&tmp_dir);
+ assert_se(!strcmp(tmp_dir, default_var_tmp));
+
+ free(tmp_dir);
+
+ if (tmpdir_backup != NULL) {
+ setenv(var_name, tmpdir_backup, do_overwrite);
+ assert_se(!strcmp(getenv(var_name), tmpdir_backup));
+ free(tmpdir_backup);
+ }
+}
+
int main(int argc, char *argv[]) {
test_unlink_noerrno();
test_readlink_and_make_absolute();
test_get_files_in_directory();
+ test_var_tmp();
return 0;
}
diff --git a/src/test/test-id128.c b/src/test/test-id128.c
index 96aa008c06..f01fbdd6b2 100644
--- a/src/test/test-id128.c
+++ b/src/test/test-id128.c
@@ -23,6 +23,9 @@
#include "sd-id128.h"
#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "id128-util.h"
#include "macro.h"
#include "string-util.h"
#include "util.h"
@@ -33,8 +36,9 @@
int main(int argc, char *argv[]) {
sd_id128_t id, id2;
- char t[33];
+ char t[33], q[37];
_cleanup_free_ char *b = NULL;
+ _cleanup_close_ int fd = -1;
assert_se(sd_id128_randomize(&id) == 0);
printf("random: %s\n", sd_id128_to_string(id, t));
@@ -57,6 +61,17 @@ int main(int argc, char *argv[]) {
printf("waldi2: %s\n", b);
assert_se(streq(t, b));
+ printf("waldi3: %s\n", id128_to_uuid_string(ID128_WALDI, q));
+ assert_se(streq(q, UUID_WALDI));
+
+ b = mfree(b);
+ assert_se(asprintf(&b, ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(ID128_WALDI)) == 36);
+ printf("waldi4: %s\n", b);
+ assert_se(streq(q, b));
+
+ assert_se(sd_id128_from_string(STR_WALDI, &id) >= 0);
+ assert_se(sd_id128_equal(id, ID128_WALDI));
+
assert_se(sd_id128_from_string(UUID_WALDI, &id) >= 0);
assert_se(sd_id128_equal(id, ID128_WALDI));
@@ -74,5 +89,69 @@ int main(int argc, char *argv[]) {
assert_se(!id128_is_valid("01020304-0506-0708-090a0b0c0d0e0f10"));
assert_se(!id128_is_valid("010203040506-0708-090a-0b0c0d0e0f10"));
+ fd = open_tmpfile_unlinkable(NULL, O_RDWR|O_CLOEXEC);
+ assert_se(fd >= 0);
+
+ /* First, write as UUID */
+ assert_se(sd_id128_randomize(&id) >= 0);
+ assert_se(id128_write_fd(fd, ID128_UUID, id, false) >= 0);
+
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(id128_read_fd(fd, ID128_PLAIN, &id2) == -EINVAL);
+
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(id128_read_fd(fd, ID128_UUID, &id2) >= 0);
+ assert_se(sd_id128_equal(id, id2));
+
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(id128_read_fd(fd, ID128_ANY, &id2) >= 0);
+ assert_se(sd_id128_equal(id, id2));
+
+ /* Second, write as plain */
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(ftruncate(fd, 0) >= 0);
+
+ assert_se(sd_id128_randomize(&id) >= 0);
+ assert_se(id128_write_fd(fd, ID128_PLAIN, id, false) >= 0);
+
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(id128_read_fd(fd, ID128_UUID, &id2) == -EINVAL);
+
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(id128_read_fd(fd, ID128_PLAIN, &id2) >= 0);
+ assert_se(sd_id128_equal(id, id2));
+
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(id128_read_fd(fd, ID128_ANY, &id2) >= 0);
+ assert_se(sd_id128_equal(id, id2));
+
+ /* Third, write plain without trailing newline */
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(ftruncate(fd, 0) >= 0);
+
+ assert_se(sd_id128_randomize(&id) >= 0);
+ assert_se(write(fd, sd_id128_to_string(id, t), 32) == 32);
+
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(id128_read_fd(fd, ID128_UUID, &id2) == -EINVAL);
+
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(id128_read_fd(fd, ID128_PLAIN, &id2) >= 0);
+ assert_se(sd_id128_equal(id, id2));
+
+ /* Third, write UUID without trailing newline */
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(ftruncate(fd, 0) >= 0);
+
+ assert_se(sd_id128_randomize(&id) >= 0);
+ assert_se(write(fd, id128_to_uuid_string(id, t), 36) == 36);
+
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(id128_read_fd(fd, ID128_PLAIN, &id2) == -EINVAL);
+
+ assert_se(lseek(fd, 0, SEEK_SET) == 0);
+ assert_se(id128_read_fd(fd, ID128_UUID, &id2) >= 0);
+ assert_se(sd_id128_equal(id, id2));
+
return 0;
}
diff --git a/src/test/test-install-root.c b/src/test/test-install-root.c
index 4b9a74fca4..db1c928660 100644
--- a/src/test/test-install-root.c
+++ b/src/test/test-install-root.c
@@ -301,7 +301,12 @@ static void test_linked_units(const char *root) {
unit_file_changes_free(changes, n_changes);
changes = NULL; n_changes = 0;
- assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("linked3.service"), false, &changes, &n_changes) == -ELOOP);
+ assert_se(unit_file_enable(UNIT_FILE_SYSTEM, false, root, STRV_MAKE("linked3.service"), false, &changes, &n_changes) >= 0);
+ assert_se(n_changes == 1);
+ assert_se(changes[0].type == UNIT_FILE_SYMLINK);
+ assert_se(startswith(changes[0].path, root));
+ assert_se(endswith(changes[0].path, "linked3.service"));
+ assert_se(streq(changes[0].source, "/opt/linked3.service"));
unit_file_changes_free(changes, n_changes);
changes = NULL; n_changes = 0;
}
diff --git a/src/test/test-nss.c b/src/test/test-nss.c
index 55af592287..c43bda5917 100644
--- a/src/test/test-nss.c
+++ b/src/test/test-nss.c
@@ -400,8 +400,8 @@ int main(int argc, char **argv) {
_cleanup_free_ char *dir = NULL, *hostname = NULL;
const char *module;
- const uint32_t local_address_ipv4 = htonl(0x7F000001);
- const uint32_t local_address_ipv4_2 = htonl(0x7F000002);
+ const uint32_t local_address_ipv4 = htobe32(0x7F000001);
+ const uint32_t local_address_ipv4_2 = htobe32(0x7F000002);
_cleanup_free_ struct local_address *addresses = NULL;
int n_addresses;
diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c
index 7d8677e17c..097c464229 100644
--- a/src/test/test-parse-util.c
+++ b/src/test/test-parse-util.c
@@ -475,6 +475,29 @@ static void test_safe_atod(void) {
assert_se(*e == ',');
}
+static void test_parse_percent(void) {
+ assert_se(parse_percent("") == -EINVAL);
+ assert_se(parse_percent("foo") == -EINVAL);
+ assert_se(parse_percent("0") == -EINVAL);
+ assert_se(parse_percent("50") == -EINVAL);
+ assert_se(parse_percent("100") == -EINVAL);
+ assert_se(parse_percent("-1") == -EINVAL);
+ assert_se(parse_percent("0%") == 0);
+ assert_se(parse_percent("55%") == 55);
+ assert_se(parse_percent("100%") == 100);
+ assert_se(parse_percent("-7%") == -ERANGE);
+ assert_se(parse_percent("107%") == -ERANGE);
+ assert_se(parse_percent("%") == -EINVAL);
+ assert_se(parse_percent("%%") == -EINVAL);
+ assert_se(parse_percent("%1") == -EINVAL);
+ assert_se(parse_percent("1%%") == -EINVAL);
+}
+
+static void test_parse_percent_unbounded(void) {
+ assert_se(parse_percent_unbounded("101%") == 101);
+ assert_se(parse_percent_unbounded("400%") == 400);
+}
+
int main(int argc, char *argv[]) {
log_parse_environment();
log_open();
@@ -488,6 +511,8 @@ int main(int argc, char *argv[]) {
test_safe_atou16();
test_safe_atoi16();
test_safe_atod();
+ test_parse_percent();
+ test_parse_percent_unbounded();
return 0;
}
diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c
index b53324b5e6..164a10d8a8 100644
--- a/src/test/test-path-util.c
+++ b/src/test/test-path-util.c
@@ -114,7 +114,8 @@ static void test_find_binary(const char *self) {
assert_se(find_binary(self, &p) == 0);
puts(p);
- assert_se(endswith(p, "/test-path-util"));
+ /* libtool might prefix the binary name with "lt-" */
+ assert_se(endswith(p, "/lt-test-path-util") || endswith(p, "/test-path-util"));
assert_se(path_is_absolute(p));
free(p);
diff --git a/src/test/test-proc-cmdline.c b/src/test/test-proc-cmdline.c
index a7a8f621a2..80ad5ed98b 100644
--- a/src/test/test-proc-cmdline.c
+++ b/src/test/test-proc-cmdline.c
@@ -23,6 +23,7 @@
#include "proc-cmdline.h"
#include "special.h"
#include "string-util.h"
+#include "util.h"
static int parse_item(const char *key, const char *value) {
assert_se(key);
@@ -36,9 +37,19 @@ static void test_parse_proc_cmdline(void) {
}
static void test_runlevel_to_target(void) {
+ in_initrd_force(false);
assert_se(streq_ptr(runlevel_to_target(NULL), NULL));
assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL));
+ assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL));
assert_se(streq_ptr(runlevel_to_target("3"), SPECIAL_MULTI_USER_TARGET));
+ assert_se(streq_ptr(runlevel_to_target("rd.rescue"), NULL));
+
+ in_initrd_force(true);
+ assert_se(streq_ptr(runlevel_to_target(NULL), NULL));
+ assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL));
+ assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL));
+ assert_se(streq_ptr(runlevel_to_target("3"), NULL));
+ assert_se(streq_ptr(runlevel_to_target("rd.rescue"), SPECIAL_RESCUE_TARGET));
}
int main(void) {
diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c
index 4616314200..9ada46b1e9 100644
--- a/src/test/test-process-util.c
+++ b/src/test/test-process-util.c
@@ -18,82 +18,88 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#include <sched.h>
+#include <sys/mount.h>
#include <sys/personality.h>
+#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
+#ifdef HAVE_VALGRIND_VALGRIND_H
+#include <valgrind/valgrind.h>
+#endif
#include "alloc-util.h"
#include "architecture.h"
+#include "fd-util.h"
#include "log.h"
#include "macro.h"
+#include "parse-util.h"
#include "process-util.h"
+#include "stdio-util.h"
#include "string-util.h"
#include "terminal-util.h"
+#include "test-helper.h"
#include "util.h"
#include "virt.h"
-static void test_get_process_comm(void) {
+static void test_get_process_comm(pid_t pid) {
struct stat st;
- _cleanup_free_ char *a = NULL, *c = NULL, *d = NULL, *f = NULL, *i = NULL, *cwd = NULL, *root = NULL;
+ _cleanup_free_ char *a = NULL, *c = NULL, *d = NULL, *f = NULL, *i = NULL;
_cleanup_free_ char *env = NULL;
+ char path[strlen("/proc//comm") + DECIMAL_STR_MAX(pid_t)];
pid_t e;
uid_t u;
gid_t g;
dev_t h;
int r;
- pid_t me;
-
- if (stat("/proc/1/comm", &st) == 0) {
- assert_se(get_process_comm(1, &a) >= 0);
- log_info("pid1 comm: '%s'", a);
- } else
- log_warning("/proc/1/comm does not exist.");
- assert_se(get_process_cmdline(1, 0, true, &c) >= 0);
- log_info("pid1 cmdline: '%s'", c);
+ xsprintf(path, "/proc/"PID_FMT"/comm", pid);
- assert_se(get_process_cmdline(1, 8, false, &d) >= 0);
- log_info("pid1 cmdline truncated: '%s'", d);
+ if (stat(path, &st) == 0) {
+ assert_se(get_process_comm(pid, &a) >= 0);
+ log_info("PID"PID_FMT" comm: '%s'", pid, a);
+ } else
+ log_warning("%s not exist.", path);
- assert_se(get_process_ppid(1, &e) >= 0);
- log_info("pid1 ppid: "PID_FMT, e);
- assert_se(e == 0);
+ assert_se(get_process_cmdline(pid, 0, true, &c) >= 0);
+ log_info("PID"PID_FMT" cmdline: '%s'", pid, c);
- assert_se(is_kernel_thread(1) == 0);
+ assert_se(get_process_cmdline(pid, 8, false, &d) >= 0);
+ log_info("PID"PID_FMT" cmdline truncated to 8: '%s'", pid, d);
- r = get_process_exe(1, &f);
- assert_se(r >= 0 || r == -EACCES);
- log_info("pid1 exe: '%s'", strna(f));
+ free(d);
+ assert_se(get_process_cmdline(pid, 1, false, &d) >= 0);
+ log_info("PID"PID_FMT" cmdline truncated to 1: '%s'", pid, d);
- assert_se(get_process_uid(1, &u) == 0);
- log_info("pid1 uid: "UID_FMT, u);
- assert_se(u == 0);
+ assert_se(get_process_ppid(pid, &e) >= 0);
+ log_info("PID"PID_FMT" PPID: "PID_FMT, pid, e);
+ assert_se(pid == 1 ? e == 0 : e > 0);
- assert_se(get_process_gid(1, &g) == 0);
- log_info("pid1 gid: "GID_FMT, g);
- assert_se(g == 0);
+ assert_se(is_kernel_thread(pid) == 0 || pid != 1);
- me = getpid();
-
- r = get_process_cwd(me, &cwd);
+ r = get_process_exe(pid, &f);
assert_se(r >= 0 || r == -EACCES);
- log_info("pid1 cwd: '%s'", cwd);
+ log_info("PID"PID_FMT" exe: '%s'", pid, strna(f));
- r = get_process_root(me, &root);
- assert_se(r >= 0 || r == -EACCES);
- log_info("pid1 root: '%s'", root);
+ assert_se(get_process_uid(pid, &u) == 0);
+ log_info("PID"PID_FMT" UID: "UID_FMT, pid, u);
+ assert_se(u == 0 || pid != 1);
- r = get_process_environ(me, &env);
+ assert_se(get_process_gid(pid, &g) == 0);
+ log_info("PID"PID_FMT" GID: "GID_FMT, pid, g);
+ assert_se(g == 0 || pid != 1);
+
+ r = get_process_environ(pid, &env);
assert_se(r >= 0 || r == -EACCES);
- log_info("self strlen(environ): '%zu'", strlen(env));
+ log_info("PID"PID_FMT" strlen(environ): %zi", pid, env ? (ssize_t)strlen(env) : (ssize_t)-errno);
if (!detect_container())
- assert_se(get_ctty_devnr(1, &h) == -ENXIO);
+ assert_se(get_ctty_devnr(pid, &h) == -ENXIO || pid != 1);
- getenv_for_pid(1, "PATH", &i);
- log_info("pid1 $PATH: '%s'", strna(i));
+ getenv_for_pid(pid, "PATH", &i);
+ log_info("PID"PID_FMT" $PATH: '%s'", pid, strna(i));
}
static void test_pid_is_unwaited(void) {
@@ -153,14 +159,213 @@ static void test_personality(void) {
#endif
}
+static void test_get_process_cmdline_harder(void) {
+ char path[] = "/tmp/test-cmdlineXXXXXX";
+ _cleanup_close_ int fd = -1;
+ _cleanup_free_ char *line = NULL;
+ pid_t pid;
+
+ if (geteuid() != 0)
+ return;
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+ /* valgrind patches open(/proc//cmdline)
+ * so, test_get_process_cmdline_harder fails always
+ * See https://github.com/systemd/systemd/pull/3555#issuecomment-226564908 */
+ if (RUNNING_ON_VALGRIND)
+ return;
+#endif
+
+ pid = fork();
+ if (pid > 0) {
+ siginfo_t si;
+
+ (void) wait_for_terminate(pid, &si);
+
+ assert_se(si.si_code == CLD_EXITED);
+ assert_se(si.si_status == 0);
+
+ return;
+ }
+
+ assert_se(pid == 0);
+ assert_se(unshare(CLONE_NEWNS) >= 0);
+
+ fd = mkostemp(path, O_CLOEXEC);
+ assert_se(fd >= 0);
+ assert_se(mount(path, "/proc/self/cmdline", "bind", MS_BIND, NULL) >= 0);
+ assert_se(unlink(path) >= 0);
+
+ assert_se(prctl(PR_SET_NAME, "testa") >= 0);
+
+ assert_se(get_process_cmdline(getpid(), 0, false, &line) == -ENOENT);
+
+ assert_se(get_process_cmdline(getpid(), 0, true, &line) >= 0);
+ assert_se(streq(line, "[testa]"));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 1, true, &line) >= 0);
+ assert_se(streq(line, ""));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 2, true, &line) >= 0);
+ assert_se(streq(line, "["));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 3, true, &line) >= 0);
+ assert_se(streq(line, "[."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 4, true, &line) >= 0);
+ assert_se(streq(line, "[.."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 5, true, &line) >= 0);
+ assert_se(streq(line, "[..."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 6, true, &line) >= 0);
+ assert_se(streq(line, "[...]"));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 7, true, &line) >= 0);
+ assert_se(streq(line, "[t...]"));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 8, true, &line) >= 0);
+ assert_se(streq(line, "[testa]"));
+ line = mfree(line);
+
+ assert_se(write(fd, "\0\0\0\0\0\0\0\0\0", 10) == 10);
+
+ assert_se(get_process_cmdline(getpid(), 0, false, &line) == -ENOENT);
+
+ assert_se(get_process_cmdline(getpid(), 0, true, &line) >= 0);
+ assert_se(streq(line, "[testa]"));
+ line = mfree(line);
+
+ assert_se(write(fd, "foo\0bar\0\0\0\0\0", 10) == 10);
+
+ assert_se(get_process_cmdline(getpid(), 0, false, &line) >= 0);
+ assert_se(streq(line, "foo bar"));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 0, true, &line) >= 0);
+ assert_se(streq(line, "foo bar"));
+ line = mfree(line);
+
+ assert_se(write(fd, "quux", 4) == 4);
+ assert_se(get_process_cmdline(getpid(), 0, false, &line) >= 0);
+ assert_se(streq(line, "foo bar quux"));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 0, true, &line) >= 0);
+ assert_se(streq(line, "foo bar quux"));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 1, true, &line) >= 0);
+ assert_se(streq(line, ""));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 2, true, &line) >= 0);
+ assert_se(streq(line, "."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 3, true, &line) >= 0);
+ assert_se(streq(line, ".."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 4, true, &line) >= 0);
+ assert_se(streq(line, "..."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 5, true, &line) >= 0);
+ assert_se(streq(line, "f..."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 6, true, &line) >= 0);
+ assert_se(streq(line, "fo..."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 7, true, &line) >= 0);
+ assert_se(streq(line, "foo..."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 8, true, &line) >= 0);
+ assert_se(streq(line, "foo..."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 9, true, &line) >= 0);
+ assert_se(streq(line, "foo b..."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 10, true, &line) >= 0);
+ assert_se(streq(line, "foo ba..."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 11, true, &line) >= 0);
+ assert_se(streq(line, "foo bar..."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 12, true, &line) >= 0);
+ assert_se(streq(line, "foo bar..."));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 13, true, &line) >= 0);
+ assert_se(streq(line, "foo bar quux"));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 14, true, &line) >= 0);
+ assert_se(streq(line, "foo bar quux"));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 1000, true, &line) >= 0);
+ assert_se(streq(line, "foo bar quux"));
+ line = mfree(line);
+
+ assert_se(ftruncate(fd, 0) >= 0);
+ assert_se(prctl(PR_SET_NAME, "aaaa bbbb cccc") >= 0);
+
+ assert_se(get_process_cmdline(getpid(), 0, false, &line) == -ENOENT);
+
+ assert_se(get_process_cmdline(getpid(), 0, true, &line) >= 0);
+ assert_se(streq(line, "[aaaa bbbb cccc]"));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 10, true, &line) >= 0);
+ assert_se(streq(line, "[aaaa...]"));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 11, true, &line) >= 0);
+ assert_se(streq(line, "[aaaa...]"));
+ line = mfree(line);
+
+ assert_se(get_process_cmdline(getpid(), 12, true, &line) >= 0);
+ assert_se(streq(line, "[aaaa b...]"));
+ line = mfree(line);
+
+ safe_close(fd);
+ _exit(0);
+}
+
int main(int argc, char *argv[]) {
log_parse_environment();
log_open();
- test_get_process_comm();
+ if (argc > 1) {
+ pid_t pid = 0;
+
+ (void) parse_pid(argv[1], &pid);
+ test_get_process_comm(pid);
+ } else {
+ TEST_REQ_RUNNING_SYSTEMD(test_get_process_comm(1));
+ test_get_process_comm(getpid());
+ }
+
test_pid_is_unwaited();
test_pid_is_alive();
test_personality();
+ test_get_process_cmdline_harder();
return 0;
}
diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c
index b480fdaa9c..1f853a7f16 100644
--- a/src/test/test-socket-util.c
+++ b/src/test/test-socket-util.c
@@ -286,6 +286,55 @@ static void test_in_addr_to_string(void) {
test_in_addr_to_string_one(AF_INET6, "fe80::");
}
+static void test_in_addr_ifindex_to_string_one(int f, const char *a, int ifindex, const char *b) {
+ _cleanup_free_ char *r = NULL;
+ union in_addr_union ua, uuaa;
+ int ff, ifindex2;
+
+ assert_se(in_addr_from_string(f, a, &ua) >= 0);
+ assert_se(in_addr_ifindex_to_string(f, &ua, ifindex, &r) >= 0);
+ printf("test_in_addr_ifindex_to_string_one: %s == %s\n", b, r);
+ assert_se(streq(b, r));
+
+ assert_se(in_addr_ifindex_from_string_auto(b, &ff, &uuaa, &ifindex2) >= 0);
+ assert_se(ff == f);
+ assert_se(in_addr_equal(f, &ua, &uuaa));
+ assert_se(ifindex2 == ifindex || ifindex2 == 0);
+}
+
+static void test_in_addr_ifindex_to_string(void) {
+ test_in_addr_ifindex_to_string_one(AF_INET, "192.168.0.1", 7, "192.168.0.1");
+ test_in_addr_ifindex_to_string_one(AF_INET, "10.11.12.13", 9, "10.11.12.13");
+ test_in_addr_ifindex_to_string_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 10, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+ test_in_addr_ifindex_to_string_one(AF_INET6, "::1", 11, "::1");
+ test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::", 12, "fe80::%12");
+ test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::", 0, "fe80::");
+ test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::14", 12, "fe80::14%12");
+ test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::15", -7, "fe80::15");
+ test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::16", LOOPBACK_IFINDEX, "fe80::16%1");
+}
+
+static void test_in_addr_ifindex_from_string_auto(void) {
+ int family, ifindex;
+ union in_addr_union ua;
+
+ /* Most in_addr_ifindex_from_string_auto() invocations have already been tested above, but let's test some more */
+
+ assert_se(in_addr_ifindex_from_string_auto("fe80::17", &family, &ua, &ifindex) >= 0);
+ assert_se(family == AF_INET6);
+ assert_se(ifindex == 0);
+
+ assert_se(in_addr_ifindex_from_string_auto("fe80::18%19", &family, &ua, &ifindex) >= 0);
+ assert_se(family == AF_INET6);
+ assert_se(ifindex == 19);
+
+ assert_se(in_addr_ifindex_from_string_auto("fe80::18%lo", &family, &ua, &ifindex) >= 0);
+ assert_se(family == AF_INET6);
+ assert_se(ifindex == LOOPBACK_IFINDEX);
+
+ assert_se(in_addr_ifindex_from_string_auto("fe80::19%thisinterfacecantexist", &family, &ua, &ifindex) == -ENODEV);
+}
+
static void *connect_thread(void *arg) {
union sockaddr_union *sa = arg;
_cleanup_close_ int fd = -1;
@@ -304,7 +353,7 @@ static void test_nameinfo_pretty(void) {
union sockaddr_union s = {
.in.sin_family = AF_INET,
.in.sin_port = 0,
- .in.sin_addr.s_addr = htonl(INADDR_ANY),
+ .in.sin_addr.s_addr = htobe32(INADDR_ANY),
};
int r;
@@ -342,17 +391,17 @@ static void test_sockaddr_equal(void) {
union sockaddr_union a = {
.in.sin_family = AF_INET,
.in.sin_port = 0,
- .in.sin_addr.s_addr = htonl(INADDR_ANY),
+ .in.sin_addr.s_addr = htobe32(INADDR_ANY),
};
union sockaddr_union b = {
.in.sin_family = AF_INET,
.in.sin_port = 0,
- .in.sin_addr.s_addr = htonl(INADDR_ANY),
+ .in.sin_addr.s_addr = htobe32(INADDR_ANY),
};
union sockaddr_union c = {
.in.sin_family = AF_INET,
.in.sin_port = 0,
- .in.sin_addr.s_addr = htonl(1234),
+ .in.sin_addr.s_addr = htobe32(1234),
};
union sockaddr_union d = {
.in6.sin6_family = AF_INET6,
@@ -398,6 +447,8 @@ int main(int argc, char *argv[]) {
test_in_addr_prefix_intersect();
test_in_addr_prefix_next();
test_in_addr_to_string();
+ test_in_addr_ifindex_to_string();
+ test_in_addr_ifindex_from_string_auto();
test_nameinfo_pretty();
diff --git a/src/test/test-strv.c b/src/test/test-strv.c
index fc01dcfaf1..841a36782f 100644
--- a/src/test/test-strv.c
+++ b/src/test/test-strv.c
@@ -647,7 +647,9 @@ static void test_strv_extend_n(void) {
static void test_strv_make_nulstr_one(char **l) {
_cleanup_free_ char *b = NULL, *c = NULL;
_cleanup_strv_free_ char **q = NULL;
+ const char *s = NULL;
size_t n, m;
+ unsigned i = 0;
assert_se(strv_make_nulstr(l, &b, &n) >= 0);
assert_se(q = strv_parse_nulstr(b, n));
@@ -656,6 +658,10 @@ static void test_strv_make_nulstr_one(char **l) {
assert_se(strv_make_nulstr(q, &c, &m) >= 0);
assert_se(m == n);
assert_se(memcmp(b, c, m) == 0);
+
+ NULSTR_FOREACH(s, b)
+ assert_se(streq(s, l[i++]));
+ assert_se(i == strv_length(l));
}
static void test_strv_make_nulstr(void) {
@@ -685,6 +691,16 @@ static void test_foreach_string(void) {
assert_se(streq(x, "zzz"));
}
+static void test_strv_fnmatch(void) {
+ _cleanup_strv_free_ char **v = NULL;
+
+ assert_se(!strv_fnmatch(STRV_MAKE_EMPTY, "a", 0));
+
+ v = strv_new("*\\*", NULL);
+ assert_se(!strv_fnmatch(v, "\\", 0));
+ assert_se(strv_fnmatch(v, "\\", FNM_NOESCAPE));
+}
+
int main(int argc, char *argv[]) {
test_specifier_printf();
test_strv_foreach();
@@ -750,6 +766,7 @@ int main(int argc, char *argv[]) {
test_strv_make_nulstr();
test_foreach_string();
+ test_strv_fnmatch();
return 0;
}
diff --git a/src/test/test-time.c b/src/test/test-time.c
index ee7d55c5ab..7078a0374d 100644
--- a/src/test/test-time.c
+++ b/src/test/test-time.c
@@ -19,6 +19,7 @@
#include "strv.h"
#include "time-util.h"
+#include "random-util.h"
static void test_parse_sec(void) {
usec_t u;
@@ -201,6 +202,48 @@ static void test_usec_sub(void) {
assert_se(usec_sub(USEC_INFINITY, 5) == USEC_INFINITY);
}
+static void test_format_timestamp(void) {
+ unsigned i;
+
+ for (i = 0; i < 100; i++) {
+ char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)];
+ usec_t x, y;
+
+ random_bytes(&x, sizeof(x));
+ x = x % (2147483600 * USEC_PER_SEC) + 1;
+
+ assert_se(format_timestamp(buf, sizeof(buf), x));
+ log_info("%s", buf);
+ assert_se(parse_timestamp(buf, &y) >= 0);
+ assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC);
+
+ assert_se(format_timestamp_utc(buf, sizeof(buf), x));
+ log_info("%s", buf);
+ assert_se(parse_timestamp(buf, &y) >= 0);
+ assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC);
+
+ assert_se(format_timestamp_us(buf, sizeof(buf), x));
+ log_info("%s", buf);
+ assert_se(parse_timestamp(buf, &y) >= 0);
+ assert_se(x == y);
+
+ assert_se(format_timestamp_us_utc(buf, sizeof(buf), x));
+ log_info("%s", buf);
+ assert_se(parse_timestamp(buf, &y) >= 0);
+ assert_se(x == y);
+
+ assert_se(format_timestamp_relative(buf, sizeof(buf), x));
+ log_info("%s", buf);
+ assert_se(parse_timestamp(buf, &y) >= 0);
+
+ /* The two calls above will run with a slightly different local time. Make sure we are in the same
+ * range however, but give enough leeway that this is unlikely to explode. And of course,
+ * format_timestamp_relative() scales the accuracy with the distance from the current time up to one
+ * month, cover for that too. */
+ assert_se(y > x ? y - x : x - y <= USEC_PER_MONTH + USEC_PER_DAY);
+ }
+}
+
int main(int argc, char *argv[]) {
uintmax_t x;
@@ -214,6 +257,7 @@ int main(int argc, char *argv[]) {
test_get_timezones();
test_usec_add();
test_usec_sub();
+ test_format_timestamp();
/* Ensure time_t is signed */
assert_cc((time_t) -1 < (time_t) 1);
diff --git a/src/test/test-unaligned.c b/src/test/test-unaligned.c
index b18b3fca0e..4f64398943 100644
--- a/src/test/test-unaligned.c
+++ b/src/test/test-unaligned.c
@@ -159,7 +159,31 @@ static void test_le(void) {
assert_se(memcmp(&scratch[7], &data[7], sizeof(uint64_t)) == 0);
}
+static void test_ne(void) {
+ uint16_t x = 4711;
+ uint32_t y = 123456;
+ uint64_t z = 9876543210;
+
+ /* Note that we don't bother actually testing alignment issues in this function, after all the _ne() functions
+ * are just aliases for the _le() or _be() implementations, which we test extensively above. Hence, in this
+ * function, just ensure that they map to the right version on the local architecture. */
+
+ assert_se(unaligned_read_ne16(&x) == 4711);
+ assert_se(unaligned_read_ne32(&y) == 123456);
+ assert_se(unaligned_read_ne64(&z) == 9876543210);
+
+ unaligned_write_ne16(&x, 1);
+ unaligned_write_ne32(&y, 2);
+ unaligned_write_ne64(&z, 3);
+
+ assert_se(x == 1);
+ assert_se(y == 2);
+ assert_se(z == 3);
+}
+
int main(int argc, const char *argv[]) {
test_be();
test_le();
+ test_ne();
+ return 0;
}
diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c
index 8d1ec19f17..2a344a9f93 100644
--- a/src/test/test-user-util.c
+++ b/src/test/test-user-util.c
@@ -61,6 +61,88 @@ static void test_uid_ptr(void) {
assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000);
}
+static void test_valid_user_group_name(void) {
+ assert_se(!valid_user_group_name(NULL));
+ assert_se(!valid_user_group_name(""));
+ assert_se(!valid_user_group_name("1"));
+ assert_se(!valid_user_group_name("65535"));
+ assert_se(!valid_user_group_name("-1"));
+ assert_se(!valid_user_group_name("-kkk"));
+ assert_se(!valid_user_group_name("rööt"));
+ assert_se(!valid_user_group_name("."));
+ assert_se(!valid_user_group_name("eff.eff"));
+ assert_se(!valid_user_group_name("foo\nbar"));
+ assert_se(!valid_user_group_name("0123456789012345678901234567890123456789"));
+ assert_se(!valid_user_group_name_or_id("aaa:bbb"));
+
+ assert_se(valid_user_group_name("root"));
+ assert_se(valid_user_group_name("lennart"));
+ assert_se(valid_user_group_name("LENNART"));
+ assert_se(valid_user_group_name("_kkk"));
+ assert_se(valid_user_group_name("kkk-"));
+ assert_se(valid_user_group_name("kk-k"));
+
+ assert_se(valid_user_group_name("some5"));
+ assert_se(!valid_user_group_name("5some"));
+ assert_se(valid_user_group_name("INNER5NUMBER"));
+}
+
+static void test_valid_user_group_name_or_id(void) {
+ assert_se(!valid_user_group_name_or_id(NULL));
+ assert_se(!valid_user_group_name_or_id(""));
+ assert_se(valid_user_group_name_or_id("0"));
+ assert_se(valid_user_group_name_or_id("1"));
+ assert_se(valid_user_group_name_or_id("65534"));
+ assert_se(!valid_user_group_name_or_id("65535"));
+ assert_se(valid_user_group_name_or_id("65536"));
+ assert_se(!valid_user_group_name_or_id("-1"));
+ assert_se(!valid_user_group_name_or_id("-kkk"));
+ assert_se(!valid_user_group_name_or_id("rööt"));
+ assert_se(!valid_user_group_name_or_id("."));
+ assert_se(!valid_user_group_name_or_id("eff.eff"));
+ assert_se(!valid_user_group_name_or_id("foo\nbar"));
+ assert_se(!valid_user_group_name_or_id("0123456789012345678901234567890123456789"));
+ assert_se(!valid_user_group_name_or_id("aaa:bbb"));
+
+ assert_se(valid_user_group_name_or_id("root"));
+ assert_se(valid_user_group_name_or_id("lennart"));
+ assert_se(valid_user_group_name_or_id("LENNART"));
+ assert_se(valid_user_group_name_or_id("_kkk"));
+ assert_se(valid_user_group_name_or_id("kkk-"));
+ assert_se(valid_user_group_name_or_id("kk-k"));
+
+ assert_se(valid_user_group_name_or_id("some5"));
+ assert_se(!valid_user_group_name_or_id("5some"));
+ assert_se(valid_user_group_name_or_id("INNER5NUMBER"));
+}
+
+static void test_valid_gecos(void) {
+
+ assert_se(!valid_gecos(NULL));
+ assert_se(valid_gecos(""));
+ assert_se(valid_gecos("test"));
+ assert_se(valid_gecos("Ümläüt"));
+ assert_se(!valid_gecos("In\nvalid"));
+ assert_se(!valid_gecos("In:valid"));
+}
+
+static void test_valid_home(void) {
+
+ assert_se(!valid_home(NULL));
+ assert_se(!valid_home(""));
+ assert_se(!valid_home("."));
+ assert_se(!valid_home("/home/.."));
+ assert_se(!valid_home("/home/../"));
+ assert_se(!valid_home("/home\n/foo"));
+ assert_se(!valid_home("./piep"));
+ assert_se(!valid_home("piep"));
+ assert_se(!valid_home("/home/user:lennart"));
+
+ assert_se(valid_home("/"));
+ assert_se(valid_home("/home"));
+ assert_se(valid_home("/home/foo"));
+}
+
int main(int argc, char*argv[]) {
test_uid_to_name_one(0, "root");
@@ -75,5 +157,10 @@ int main(int argc, char*argv[]) {
test_parse_uid();
test_uid_ptr();
+ test_valid_user_group_name();
+ test_valid_user_group_name_or_id();
+ test_valid_gecos();
+ test_valid_home();
+
return 0;
}
diff --git a/src/test/test-util.c b/src/test/test-util.c
index 9b6d2a7968..1b5cba86c1 100644
--- a/src/test/test-util.c
+++ b/src/test/test-util.c
@@ -26,6 +26,7 @@
#include "def.h"
#include "fileio.h"
#include "fs-util.h"
+#include "parse-util.h"
#include "raw-clone.h"
#include "rm-rf.h"
#include "string-util.h"
@@ -263,6 +264,89 @@ static void test_raw_clone(void) {
}
}
+static void test_physical_memory(void) {
+ uint64_t p;
+ char buf[FORMAT_BYTES_MAX];
+
+ p = physical_memory();
+ assert_se(p > 0);
+ assert_se(p < UINT64_MAX);
+ assert_se(p % page_size() == 0);
+
+ log_info("Memory: %s (%" PRIu64 ")", format_bytes(buf, sizeof(buf), p), p);
+}
+
+static void test_physical_memory_scale(void) {
+ uint64_t p;
+
+ p = physical_memory();
+
+ assert_se(physical_memory_scale(0, 100) == 0);
+ assert_se(physical_memory_scale(100, 100) == p);
+
+ log_info("Memory original: %" PRIu64, physical_memory());
+ log_info("Memory scaled by 50%%: %" PRIu64, physical_memory_scale(50, 100));
+ log_info("Memory divided by 2: %" PRIu64, physical_memory() / 2);
+ log_info("Page size: %zu", page_size());
+
+ /* There might be an uneven number of pages, hence permit these calculations to be half a page off... */
+ assert_se(page_size()/2 + physical_memory_scale(50, 100) - p/2 <= page_size());
+ assert_se(physical_memory_scale(200, 100) == p*2);
+
+ assert_se(physical_memory_scale(0, 1) == 0);
+ assert_se(physical_memory_scale(1, 1) == p);
+ assert_se(physical_memory_scale(2, 1) == p*2);
+
+ assert_se(physical_memory_scale(0, 2) == 0);
+
+ assert_se(page_size()/2 + physical_memory_scale(1, 2) - p/2 <= page_size());
+ assert_se(physical_memory_scale(2, 2) == p);
+ assert_se(physical_memory_scale(4, 2) == p*2);
+
+ assert_se(physical_memory_scale(0, UINT32_MAX) == 0);
+ assert_se(physical_memory_scale(UINT32_MAX, UINT32_MAX) == p);
+
+ /* overflow */
+ assert_se(physical_memory_scale(UINT64_MAX/4, UINT64_MAX) == UINT64_MAX);
+}
+
+static void test_system_tasks_max(void) {
+ uint64_t t;
+
+ t = system_tasks_max();
+ assert_se(t > 0);
+ assert_se(t < UINT64_MAX);
+
+ log_info("Max tasks: %" PRIu64, t);
+}
+
+static void test_system_tasks_max_scale(void) {
+ uint64_t t;
+
+ t = system_tasks_max();
+
+ assert_se(system_tasks_max_scale(0, 100) == 0);
+ assert_se(system_tasks_max_scale(100, 100) == t);
+
+ assert_se(system_tasks_max_scale(0, 1) == 0);
+ assert_se(system_tasks_max_scale(1, 1) == t);
+ assert_se(system_tasks_max_scale(2, 1) == 2*t);
+
+ assert_se(system_tasks_max_scale(0, 2) == 0);
+ assert_se(system_tasks_max_scale(1, 2) == t/2);
+ assert_se(system_tasks_max_scale(2, 2) == t);
+ assert_se(system_tasks_max_scale(3, 2) == (3*t)/2);
+ assert_se(system_tasks_max_scale(4, 2) == t*2);
+
+ assert_se(system_tasks_max_scale(0, UINT32_MAX) == 0);
+ assert_se(system_tasks_max_scale((UINT32_MAX-1)/2, UINT32_MAX-1) == t/2);
+ assert_se(system_tasks_max_scale(UINT32_MAX, UINT32_MAX) == t);
+
+ /* overflow */
+
+ assert_se(system_tasks_max_scale(UINT64_MAX/4, UINT64_MAX) == UINT64_MAX);
+}
+
int main(int argc, char *argv[]) {
log_parse_environment();
log_open();
@@ -277,6 +361,10 @@ int main(int argc, char *argv[]) {
test_log2i();
test_execute_directory();
test_raw_clone();
+ test_physical_memory();
+ test_physical_memory_scale();
+ test_system_tasks_max();
+ test_system_tasks_max_scale();
return 0;
}
diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c
index b7871f81aa..553ef67011 100644
--- a/src/timedate/timedatectl.c
+++ b/src/timedate/timedatectl.c
@@ -57,11 +57,11 @@ typedef struct StatusInfo {
char *timezone;
usec_t rtc_time;
- bool rtc_local;
+ int rtc_local;
- bool ntp_enabled;
- bool ntp_capable;
- bool ntp_synced;
+ int ntp_enabled;
+ int ntp_capable;
+ int ntp_synced;
} StatusInfo;
static void status_info_clear(StatusInfo *info) {
@@ -480,7 +480,7 @@ static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) {
}
int main(int argc, char *argv[]) {
- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ sd_bus *bus = NULL;
int r;
setlocale(LC_ALL, "");
@@ -500,6 +500,7 @@ int main(int argc, char *argv[]) {
r = timedatectl_main(bus, argc, argv);
finish:
+ sd_bus_flush_close_unref(bus);
pager_close();
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c
index ffec609c69..490929e93b 100644
--- a/src/timedate/timedated.c
+++ b/src/timedate/timedated.c
@@ -637,7 +637,7 @@ static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error
return r;
c->use_ntp = enabled;
- log_info("Set NTP to %s", enabled ? "enabled" : "disabled");
+ log_info("Set NTP to %sd", enable_disable(enabled));
(void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index 79ccf9fad9..954f4aa985 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -1575,13 +1575,12 @@ static int clean_item_instance(Item *i, const char* instance) {
d = opendir_nomod(instance);
if (!d) {
- if (errno == ENOENT || errno == ENOTDIR) {
+ if (IN_SET(errno, ENOENT, ENOTDIR)) {
log_debug_errno(errno, "Directory \"%s\": %m", instance);
return 0;
}
- log_error_errno(errno, "Failed to open directory %s: %m", instance);
- return -errno;
+ return log_error_errno(errno, "Failed to open directory %s: %m", instance);
}
if (fstat(dirfd(d), &s) < 0)
@@ -2178,7 +2177,7 @@ static int read_config_file(const char *fn, bool ignore_enoent) {
Iterator iterator;
unsigned v = 0;
Item *i;
- int r;
+ int r = 0;
assert(fn);
diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c
index ed0ea5ce5f..3c58445836 100644
--- a/src/udev/udev-builtin-blkid.c
+++ b/src/udev/udev-builtin-blkid.c
@@ -147,11 +147,6 @@ static int find_gpt_root(struct udev_device *dev, blkid_probe pr, bool test) {
if (sd_id128_equal(type, GPT_ESP)) {
sd_id128_t id, esp;
- unsigned long long flags;
-
- flags = blkid_partition_get_flags(pp);
- if (flags & GPT_FLAG_NO_AUTO)
- continue;
/* We found an ESP, let's see if it matches
* the ESP we booted from. */
@@ -167,6 +162,11 @@ static int find_gpt_root(struct udev_device *dev, blkid_probe pr, bool test) {
found_esp = true;
} else if (sd_id128_equal(type, GPT_ROOT_NATIVE)) {
+ unsigned long long flags;
+
+ flags = blkid_partition_get_flags(pp);
+ if (flags & GPT_FLAG_NO_AUTO)
+ continue;
/* We found a suitable root partition, let's
* remember the first one. */
diff --git a/src/udev/udev-builtin-input_id.c b/src/udev/udev-builtin-input_id.c
index 51a55cdbc4..59b9804dc4 100644
--- a/src/udev/udev-builtin-input_id.c
+++ b/src/udev/udev-builtin-input_id.c
@@ -210,8 +210,14 @@ static bool test_pointers(struct udev_device *dev,
else if (has_joystick_axes_or_buttons)
is_joystick = true;
}
- if (has_mt_coordinates && (is_direct || has_touch))
- is_touchscreen = true;
+ if (has_mt_coordinates) {
+ if (stylus_or_pen)
+ is_tablet = true;
+ else if (finger_but_no_pen && !is_direct)
+ is_touchpad = true;
+ else if (has_touch || is_direct)
+ is_touchscreen = true;
+ }
if (has_rel_coordinates && has_mouse_button)
is_mouse = true;
diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c
index 8d601c9c2c..54cd741bb1 100644
--- a/src/udev/udev-event.c
+++ b/src/udev/udev-event.c
@@ -249,7 +249,7 @@ subst:
if (event->program_result == NULL)
break;
- /* get part part of the result string */
+ /* get part of the result string */
i = 0;
if (attr != NULL)
i = strtoul(attr, &rest, 10);
diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c
index 948ad0f5a5..1bffe8e8ab 100644
--- a/src/udev/udevadm-hwdb.c
+++ b/src/udev/udevadm-hwdb.c
@@ -28,6 +28,8 @@
#include "fs-util.h"
#include "hwdb-internal.h"
#include "hwdb-util.h"
+#include "label.h"
+#include "mkdir.h"
#include "strbuf.h"
#include "string-util.h"
#include "udev.h"
@@ -656,12 +658,16 @@ static int adm_hwdb(struct udev *udev, int argc, char *argv[]) {
rc = EXIT_FAILURE;
goto out;
}
- mkdir_parents(hwdb_bin, 0755);
+
+ mkdir_parents_label(hwdb_bin, 0755);
+
err = trie_store(trie, hwdb_bin);
if (err < 0) {
log_error_errno(err, "Failure writing database %s: %m", hwdb_bin);
rc = EXIT_FAILURE;
}
+
+ label_fix(hwdb_bin, false, false);
}
if (test) {
diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c
index 66b51c1209..6753c52005 100644
--- a/src/udev/udevadm-info.c
+++ b/src/udev/udevadm-info.c
@@ -433,17 +433,13 @@ static int uinfo(struct udev *udev, int argc, char *argv[]) {
case QUERY_PROPERTY:
list_entry = udev_device_get_properties_list_entry(device);
while (list_entry != NULL) {
- if (export) {
- const char *prefix = export_prefix;
-
- if (prefix == NULL)
- prefix = "";
- printf("%s%s='%s'\n", prefix,
+ if (export)
+ printf("%s%s='%s'\n", strempty(export_prefix),
udev_list_entry_get_name(list_entry),
udev_list_entry_get_value(list_entry));
- } else {
+ else
printf("%s=%s\n", udev_list_entry_get_name(list_entry), udev_list_entry_get_value(list_entry));
- }
+
list_entry = udev_list_entry_get_next(list_entry);
}
break;
diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c
index c0ef073476..f656c2198e 100644
--- a/src/udev/udevadm-monitor.c
+++ b/src/udev/udevadm-monitor.c
@@ -151,6 +151,9 @@ static int adm_monitor(struct udev *udev, int argc, char *argv[]) {
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
+ /* Callers are expecting to see events as they happen: Line buffering */
+ setlinebuf(stdout);
+
fd_ep = epoll_create1(EPOLL_CLOEXEC);
if (fd_ep < 0) {
log_error_errno(errno, "error creating epoll fd: %m");
diff --git a/src/udev/udevd.c b/src/udev/udevd.c
index a8ab208816..a893a2b3d9 100644
--- a/src/udev/udevd.c
+++ b/src/udev/udevd.c
@@ -1256,7 +1256,7 @@ static int on_post(sd_event_source *s, void *userdata) {
return r;
} else if (manager->cgroup)
/* cleanup possible left-over processes in our cgroup */
- cg_kill(SYSTEMD_CGROUP_CONTROLLER, manager->cgroup, SIGKILL, false, true, NULL);
+ cg_kill(SYSTEMD_CGROUP_CONTROLLER, manager->cgroup, SIGKILL, CGROUP_IGNORE_SELF, NULL, NULL, NULL);
}
}
diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c
index 1118118450..c0d76f9685 100644
--- a/src/vconsole/vconsole-setup.c
+++ b/src/vconsole/vconsole-setup.c
@@ -2,6 +2,7 @@
This file is part of systemd.
Copyright 2010 Kay Sievers
+ Copyright 2016 Michal Soltys <soltys@ziu.info>
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
@@ -27,6 +28,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
+#include <termios.h>
#include <unistd.h>
#include "alloc-util.h"
@@ -50,67 +52,85 @@ static bool is_vconsole(int fd) {
return ioctl(fd, TIOCLINUX, data) >= 0;
}
-static int disable_utf8(int fd) {
- int r = 0, k;
+static bool is_allocated(unsigned int idx) {
+ char vcname[strlen("/dev/vcs") + DECIMAL_STR_MAX(int)];
- if (ioctl(fd, KDSKBMODE, K_XLATE) < 0)
- r = -errno;
-
- k = loop_write(fd, "\033%@", 3, false);
- if (k < 0)
- r = k;
+ xsprintf(vcname, "/dev/vcs%i", idx);
+ return access(vcname, F_OK) == 0;
+}
- k = write_string_file("/sys/module/vt/parameters/default_utf8", "0", 0);
- if (k < 0)
- r = k;
+static bool is_allocated_byfd(int fd) {
+ struct vt_stat vcs = {};
- if (r < 0)
- log_warning_errno(r, "Failed to disable UTF-8: %m");
+ if (ioctl(fd, VT_GETSTATE, &vcs) < 0) {
+ log_warning_errno(errno, "VT_GETSTATE failed: %m");
+ return false;
+ }
+ return is_allocated(vcs.v_active);
+}
- return r;
+static bool is_settable(int fd) {
+ int r, curr_mode;
+
+ r = ioctl(fd, KDGKBMODE, &curr_mode);
+ /*
+ * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode.
+ * Oterwise we would (likely) interfere with X11's processing of the
+ * key events.
+ *
+ * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
+ */
+ return r == 0 && IN_SET(curr_mode, K_XLATE, K_UNICODE);
}
-static int enable_utf8(int fd) {
- int r = 0, k;
- long current = 0;
-
- if (ioctl(fd, KDGKBMODE, &current) < 0 || current == K_XLATE) {
- /*
- * Change the current keyboard to unicode, unless it
- * is currently in raw or off mode anyway. We
- * shouldn't interfere with X11's processing of the
- * key events.
- *
- * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
- *
- */
-
- if (ioctl(fd, KDSKBMODE, K_UNICODE) < 0)
- r = -errno;
+static int toggle_utf8(const char *name, int fd, bool utf8) {
+ int r;
+ struct termios tc = {};
+
+ assert(name);
+
+ r = ioctl(fd, KDSKBMODE, utf8 ? K_UNICODE : K_XLATE);
+ if (r < 0)
+ return log_warning_errno(errno, "Failed to %s UTF-8 kbdmode on %s: %m", enable_disable(utf8), name);
+
+ r = loop_write(fd, utf8 ? "\033%G" : "\033%@", 3, false);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to %s UTF-8 term processing on %s: %m", enable_disable(utf8), name);
+
+ r = tcgetattr(fd, &tc);
+ if (r >= 0) {
+ if (utf8)
+ tc.c_iflag |= IUTF8;
+ else
+ tc.c_iflag &= ~IUTF8;
+ r = tcsetattr(fd, TCSANOW, &tc);
}
+ if (r < 0)
+ return log_warning_errno(errno, "Failed to %s iutf8 flag on %s: %m", enable_disable(utf8), name);
- k = loop_write(fd, "\033%G", 3, false);
- if (k < 0)
- r = k;
+ log_debug("UTF-8 kbdmode %sd on %s", enable_disable(utf8), name);
+ return 0;
+}
- k = write_string_file("/sys/module/vt/parameters/default_utf8", "1", 0);
- if (k < 0)
- r = k;
+static int toggle_utf8_sysfs(bool utf8) {
+ int r;
+ r = write_string_file("/sys/module/vt/parameters/default_utf8", one_zero(utf8), 0);
if (r < 0)
- log_warning_errno(r, "Failed to enable UTF-8: %m");
+ return log_warning_errno(r, "Failed to %s sysfs UTF-8 flag: %m", enable_disable(utf8));
- return r;
+ log_debug("Sysfs UTF-8 flag %sd", enable_disable(utf8));
+ return 0;
}
static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) {
const char *args[8];
- int i = 0, r;
+ int i = 0;
pid_t pid;
/* An empty map means kernel map */
if (isempty(map))
- return 1;
+ return 0;
args[i++] = KBD_LOADKEYS;
args[i++] = "-q";
@@ -135,34 +155,31 @@ static int keyboard_load_and_wait(const char *vc, const char *map, const char *m
_exit(EXIT_FAILURE);
}
- r = wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true);
- if (r < 0)
- return r;
-
- return r == 0;
+ return wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true);
}
static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) {
const char *args[9];
- int i = 0, r;
+ int i = 0;
pid_t pid;
- /* An empty font means kernel font */
- if (isempty(font))
- return 1;
+ /* Any part can be set independently */
+ if (isempty(font) && isempty(map) && isempty(unimap))
+ return 0;
args[i++] = KBD_SETFONT;
args[i++] = "-C";
args[i++] = vc;
- args[i++] = font;
- if (map) {
+ if (!isempty(map)) {
args[i++] = "-m";
args[i++] = map;
}
- if (unimap) {
+ if (!isempty(unimap)) {
args[i++] = "-u";
args[i++] = unimap;
}
+ if (!isempty(font))
+ args[i++] = font;
args[i++] = NULL;
pid = fork();
@@ -177,11 +194,7 @@ static int font_load_and_wait(const char *vc, const char *font, const char *map,
_exit(EXIT_FAILURE);
}
- r = wait_for_terminate_and_warn(KBD_SETFONT, pid, true);
- if (r < 0)
- return r;
-
- return r == 0;
+ return wait_for_terminate_and_warn(KBD_SETFONT, pid, true);
}
/*
@@ -189,13 +202,21 @@ static int font_load_and_wait(const char *vc, const char *font, const char *map,
* we update all possibly already allocated VTs with the configured
* font. It also allows to restart systemd-vconsole-setup.service,
* to apply a new font to all VTs.
+ *
+ * We also setup per-console utf8 related stuff: kbdmode, term
+ * processing, stty iutf8.
*/
-static void font_copy_to_all_vcs(int fd) {
+static void setup_remaining_vcs(int fd, bool utf8) {
+ struct console_font_op cfo = {
+ .op = KD_FONT_OP_GET, .flags = 0,
+ .width = 32, .height = 32,
+ .charcount = 512,
+ };
struct vt_stat vcs = {};
- unsigned char map8[E_TABSZ];
- unsigned short map16[E_TABSZ];
+ struct unimapinit adv = {};
struct unimapdesc unimapd;
_cleanup_free_ struct unipair* unipairs = NULL;
+ _cleanup_free_ void *fontbuf = NULL;
int i, r;
unipairs = new(struct unipair, USHRT_MAX);
@@ -204,55 +225,79 @@ static void font_copy_to_all_vcs(int fd) {
return;
}
+ fontbuf = malloc(cfo.width * cfo.height * cfo.charcount / 8);
+ if (!fontbuf) {
+ log_oom();
+ return;
+ }
+
/* get active, and 16 bit mask of used VT numbers */
r = ioctl(fd, VT_GETSTATE, &vcs);
if (r < 0) {
- log_debug_errno(errno, "VT_GETSTATE failed, ignoring: %m");
+ log_warning_errno(errno, "VT_GETSTATE failed, ignoring remaining consoles: %m");
return;
}
- for (i = 1; i <= 15; i++) {
- char vcname[strlen("/dev/vcs") + DECIMAL_STR_MAX(int)];
- _cleanup_close_ int vcfd = -1;
- struct console_font_op cfo = {};
+ /* get fonts from source console */
+ cfo.data = fontbuf;
+ r = ioctl(fd, KDFONTOP, &cfo);
+ if (r < 0)
+ log_warning_errno(errno, "KD_FONT_OP_GET failed, fonts will not be copied: %m");
+ else {
+ unimapd.entries = unipairs;
+ unimapd.entry_ct = USHRT_MAX;
+ r = ioctl(fd, GIO_UNIMAP, &unimapd);
+ if (r < 0)
+ log_warning_errno(errno, "GIO_UNIMAP failed, fonts will not be copied: %m");
+ else
+ cfo.op = KD_FONT_OP_SET;
+ }
+
+ for (i = 1; i <= 63; i++) {
+ char ttyname[strlen("/dev/tty") + DECIMAL_STR_MAX(int)];
+ _cleanup_close_ int fd_d = -1;
- if (i == vcs.v_active)
+ if (i == vcs.v_active || !is_allocated(i))
continue;
- /* skip non-allocated ttys */
- xsprintf(vcname, "/dev/vcs%i", i);
- if (access(vcname, F_OK) < 0)
+ /* try to open terminal */
+ xsprintf(ttyname, "/dev/tty%i", i);
+ fd_d = open_terminal(ttyname, O_RDWR|O_CLOEXEC);
+ if (fd_d < 0) {
+ log_warning_errno(fd_d, "Unable to open tty%i, fonts will not be copied: %m", i);
continue;
+ }
- xsprintf(vcname, "/dev/tty%i", i);
- vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC);
- if (vcfd < 0)
+ if (!is_settable(fd_d))
continue;
- /* copy font from active VT, where the font was uploaded to */
- cfo.op = KD_FONT_OP_COPY;
- cfo.height = vcs.v_active-1; /* tty1 == index 0 */
- (void) ioctl(vcfd, KDFONTOP, &cfo);
+ toggle_utf8(ttyname, fd_d, utf8);
- /* copy map of 8bit chars */
- if (ioctl(fd, GIO_SCRNMAP, map8) >= 0)
- (void) ioctl(vcfd, PIO_SCRNMAP, map8);
+ if (cfo.op != KD_FONT_OP_SET)
+ continue;
- /* copy map of 8bit chars -> 16bit Unicode values */
- if (ioctl(fd, GIO_UNISCRNMAP, map16) >= 0)
- (void) ioctl(vcfd, PIO_UNISCRNMAP, map16);
+ r = ioctl(fd_d, KDFONTOP, &cfo);
+ if (r < 0) {
+ log_warning_errno(errno, "KD_FONT_OP_SET failed, fonts will not be copied to tty%i: %m", i);
+ continue;
+ }
/* copy unicode translation table */
/* unimapd is a ushort count and a pointer to an
array of struct unipair { ushort, ushort } */
- unimapd.entries = unipairs;
- unimapd.entry_ct = USHRT_MAX;
- if (ioctl(fd, GIO_UNIMAP, &unimapd) >= 0) {
- struct unimapinit adv = { 0, 0, 0 };
+ r = ioctl(fd_d, PIO_UNIMAPCLR, &adv);
+ if (r < 0) {
+ log_warning_errno(errno, "PIO_UNIMAPCLR failed, unimaps might be incorrect for tty%i: %m", i);
+ continue;
+ }
- (void) ioctl(vcfd, PIO_UNIMAPCLR, &adv);
- (void) ioctl(vcfd, PIO_UNIMAP, &unimapd);
+ r = ioctl(fd_d, PIO_UNIMAP, &unimapd);
+ if (r < 0) {
+ log_warning_errno(errno, "PIO_UNIMAP failed, unimaps might be incorrect for tty%i: %m", i);
+ continue;
}
+
+ log_debug("Font and unimap successfully copied to %s", ttyname);
}
}
@@ -289,6 +334,16 @@ int main(int argc, char **argv) {
return EXIT_FAILURE;
}
+ if (!is_allocated_byfd(fd)) {
+ log_error("Virtual console %s is not allocated.", vc);
+ return EXIT_FAILURE;
+ }
+
+ if (!is_settable(fd)) {
+ log_error("Virtual console %s is not in K_XLATE or K_UNICODE.", vc);
+ return EXIT_FAILURE;
+ }
+
utf8 = is_locale_utf8();
r = parse_env_file("/etc/vconsole.conf", NEWLINE,
@@ -306,8 +361,12 @@ int main(int argc, char **argv) {
if (detect_container() <= 0) {
r = parse_env_file("/proc/cmdline", WHITESPACE,
"vconsole.keymap", &vc_keymap,
- "vconsole.keymap.toggle", &vc_keymap_toggle,
+ "vconsole.keymap_toggle", &vc_keymap_toggle,
"vconsole.font", &vc_font,
+ "vconsole.font_map", &vc_font_map,
+ "vconsole.font_unimap", &vc_font_unimap,
+ /* compatibility with obsolete multiple-dot scheme */
+ "vconsole.keymap.toggle", &vc_keymap_toggle,
"vconsole.font.map", &vc_font_map,
"vconsole.font.unimap", &vc_font_unimap,
NULL);
@@ -316,17 +375,17 @@ int main(int argc, char **argv) {
log_warning_errno(r, "Failed to read /proc/cmdline: %m");
}
- if (utf8)
- (void) enable_utf8(fd);
- else
- (void) disable_utf8(fd);
-
- font_ok = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap) > 0;
- keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) > 0;
+ toggle_utf8_sysfs(utf8);
+ toggle_utf8(vc, fd, utf8);
+ font_ok = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap) == 0;
+ keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) == 0;
- /* Only copy the font when we executed setfont successfully */
- if (font_copy && font_ok)
- (void) font_copy_to_all_vcs(fd);
+ if (font_copy) {
+ if (font_ok)
+ setup_remaining_vcs(fd, utf8);
+ else
+ log_warning("Setting source virtual console failed, ignoring remaining ones");
+ }
return font_ok && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE;
}